You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gristlabs_grist-core/app/common/GranularAccessClause.ts

131 lines
3.9 KiB

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 |
GranularAccessColumnClause |
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 control access to columns within a specific table.
*/
export interface GranularAccessColumnClause {
kind: 'column';
tableId: string;
colIds: string[];
match: MatchSpec;
onMatch?: AccessPermissionDelta; // permissions to apply if match succeeds
onFail?: AccessPermissionDelta; // permissions to apply if match fails
}
/**
* 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
}
/**
* A sketch of permissions, intended as a placeholder.
*/
export type AccessPermission = 'read' | 'update' | 'create' | 'delete';
export type AccessPermissions = 'all' | AccessPermission[];
export interface AccessPermissionDelta {
allow?: AccessPermissions; // permit the named operations
allowOnly?: AccessPermissions; // permit the named operations, and forbid others
forbid?: AccessPermissions; // forbid the named operations
}
// 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;
}