diff --git a/app/common/ACLRulesReader.ts b/app/common/ACLRulesReader.ts index ed4154ce..741a719e 100644 --- a/app/common/ACLRulesReader.ts +++ b/app/common/ACLRulesReader.ts @@ -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. diff --git a/app/common/DocActions.ts b/app/common/DocActions.ts index b89246e7..8770f67d 100644 --- a/app/common/DocActions.ts +++ b/app/common/DocActions.ts @@ -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 }; diff --git a/app/common/UserAPI.ts b/app/common/UserAPI.ts index 9b1cccdb..df4d0788 100644 --- a/app/common/UserAPI.ts +++ b/app/common/UserAPI.ts @@ -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; getRecords(tableId: string, options?: GetRowsParams): Promise; + sql(sql: string, args?: any[]): Promise; updateRows(tableId: string, changes: TableColValues): Promise; addRows(tableId: string, additions: BulkColValues): Promise; removeRows(tableId: string, removals: number[]): Promise; @@ -925,6 +931,16 @@ export class DocAPIImpl extends BaseAPI implements DocAPI { return response.records; } + public async sql(sql: string, args?: any[]): Promise { + return this.requestJson(`${this._url}/sql`, { + body: JSON.stringify({ + sql, + ...(args ? { args } : {}), + }), + method: 'POST', + }); + } + public async updateRows(tableId: string, changes: TableColValues): Promise { return this.requestJson(`${this._url}/tables/${tableId}/data`, { body: JSON.stringify(changes), diff --git a/test/fixtures/docs/FilmsWithImages.grist b/test/fixtures/docs/FilmsWithImages.grist index 3881b6cf..2fdb5786 100644 Binary files a/test/fixtures/docs/FilmsWithImages.grist and b/test/fixtures/docs/FilmsWithImages.grist differ