mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
c879393a8e
Summary: This is a prototype for expanding the conditions that can be used in granular ACLs. When processing ACLs, the following variables (called "characteristics") are now available in conditions: * UserID * Email * Name * Access (owners, editors, viewers) The set of variables can be expanded by adding a "characteristic" clause. This is a clause which specifies: * A tableId * The name of an existing characteristic * A colId The effect of the clause is to expand the available characteristics with all the columns in the table, with values taken from the record where there is a match between the specified characteristic and the specified column. Existing clauses are generalized somewhat to demonstrate and test the use these variables. That isn't the main point of this diff though, and I propose to leave generalizing+systematizing those clauses for a future diff. Issues I'm not dealing with here: * How clauses combine. (The scope on GranularAccessRowClause is a hack to save me worrying about that yet). * The full set of matching methods we'll allow. * Refreshing row access in clients when the tables mentioned in characteristic tables change. * Full CRUD permission control. * Default rules (part of combination). * Reporting errors in access rules. That said, with this diff it is possible to e.g. assign a City to editors by their email address or name, and have only rows for those Cities be visible in their client. Ability to modify those rows, and remain updates about them, remains under incomplete control. Test Plan: added tests Reviewers: dsagal Reviewed By: dsagal Differential Revision: https://phab.getgrist.com/D2642
107 lines
3.1 KiB
TypeScript
107 lines
3.1 KiB
TypeScript
import { safeJsonParse } from 'app/common/gutil';
|
|
import { CellValue } from 'app/plugin/GristData';
|
|
|
|
/**
|
|
* All possible access clauses. In future the clauses will become more generalized.
|
|
* The consequences of clauses are currently combined in a naive and ad-hoc way,
|
|
* this will need systematizing.
|
|
*/
|
|
export type GranularAccessClause =
|
|
GranularAccessDocClause |
|
|
GranularAccessTableClause |
|
|
GranularAccessRowClause |
|
|
GranularAccessCharacteristicsClause;
|
|
|
|
/**
|
|
* A clause that forbids anyone but owners from modifying the document structure.
|
|
*/
|
|
export interface GranularAccessDocClause {
|
|
kind: 'doc';
|
|
match: MatchSpec;
|
|
}
|
|
|
|
/**
|
|
* A clause to control access to a specific table.
|
|
*/
|
|
export interface GranularAccessTableClause {
|
|
kind: 'table';
|
|
tableId: string;
|
|
match: MatchSpec;
|
|
}
|
|
|
|
/**
|
|
* A clause to control access to rows within a specific table.
|
|
* If "scope" is provided, this rule is simply ignored if the scope does not match
|
|
* the user.
|
|
*/
|
|
export interface GranularAccessRowClause {
|
|
kind: 'row';
|
|
tableId: string;
|
|
match: MatchSpec;
|
|
scope?: MatchSpec;
|
|
}
|
|
|
|
/**
|
|
* A clause to make more information about the user/request available for access
|
|
* control decisions.
|
|
* - charId specifies a property of the user (e.g. Access/Email/UserID/Name, or a
|
|
* property added by another clause) to use as a key.
|
|
* - We look for a matching record in the specified table, comparing the specified
|
|
* column with the charId property. Outcome is currently unspecified if there are
|
|
* multiple matches.
|
|
* - Compare using lower case for now (because of Email). Could generalize in future.
|
|
* - All fields from a matching record are added to the variables available for MatchSpecs.
|
|
*/
|
|
export interface GranularAccessCharacteristicsClause {
|
|
kind: 'character';
|
|
tableId: string;
|
|
charId: string; // characteristic to look up
|
|
lookupColId: string; // column in which to look it up
|
|
}
|
|
|
|
// Type for expressing matches.
|
|
export type MatchSpec = ConstMatchSpec | TruthyMatchSpec | PairMatchSpec | NotMatchSpec;
|
|
|
|
// Invert a match.
|
|
export interface NotMatchSpec {
|
|
kind: 'not';
|
|
match: MatchSpec;
|
|
}
|
|
|
|
// Compare property of user with a constant.
|
|
export interface ConstMatchSpec {
|
|
kind: 'const';
|
|
charId: string;
|
|
value: CellValue;
|
|
}
|
|
|
|
// Check if a table column is truthy.
|
|
export interface TruthyMatchSpec {
|
|
kind: 'truthy';
|
|
colId: string;
|
|
}
|
|
|
|
// Check if a property of user matches a table column.
|
|
export interface PairMatchSpec {
|
|
kind: 'pair';
|
|
charId: string;
|
|
colId: string;
|
|
}
|
|
|
|
// Convert a clause to a string. Trivial, but fluid currently.
|
|
export function serializeClause(clause: GranularAccessClause) {
|
|
return '~acl ' + JSON.stringify(clause);
|
|
}
|
|
|
|
export function decodeClause(code: string): GranularAccessClause|null {
|
|
// TODO: be strict about format. But it isn't super-clear what to do with
|
|
// a document if access control gets corrupted. Maybe go into an emergency
|
|
// mode where only owners have access, and they have unrestricted access?
|
|
// Also, format should be plain JSON once no longer stored in a random
|
|
// reused column.
|
|
if (code.startsWith('~acl ')) {
|
|
return safeJsonParse(code.slice(5), null);
|
|
}
|
|
return null;
|
|
}
|