2020-10-19 14:25:21 +00:00
|
|
|
import { safeJsonParse } from 'app/common/gutil';
|
|
|
|
import { CellValue } from 'app/plugin/GristData';
|
|
|
|
|
2020-10-12 13:50:07 +00:00
|
|
|
/**
|
2020-10-19 14:25:21 +00:00
|
|
|
* 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.
|
2020-10-12 13:50:07 +00:00
|
|
|
*/
|
|
|
|
export type GranularAccessClause =
|
|
|
|
GranularAccessDocClause |
|
|
|
|
GranularAccessTableClause |
|
2020-10-19 14:25:21 +00:00
|
|
|
GranularAccessRowClause |
|
2020-11-03 23:44:09 +00:00
|
|
|
GranularAccessColumnClause |
|
2020-10-19 14:25:21 +00:00
|
|
|
GranularAccessCharacteristicsClause;
|
2020-10-12 13:50:07 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* A clause that forbids anyone but owners from modifying the document structure.
|
|
|
|
*/
|
|
|
|
export interface GranularAccessDocClause {
|
|
|
|
kind: 'doc';
|
2020-10-19 14:25:21 +00:00
|
|
|
match: MatchSpec;
|
2020-10-12 13:50:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-10-19 14:25:21 +00:00
|
|
|
* A clause to control access to a specific table.
|
2020-10-12 13:50:07 +00:00
|
|
|
*/
|
|
|
|
export interface GranularAccessTableClause {
|
|
|
|
kind: 'table';
|
|
|
|
tableId: string;
|
2020-10-19 14:25:21 +00:00
|
|
|
match: MatchSpec;
|
2020-10-12 13:50:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-10-19 14:25:21 +00:00
|
|
|
* 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.
|
2020-10-12 13:50:07 +00:00
|
|
|
*/
|
|
|
|
export interface GranularAccessRowClause {
|
|
|
|
kind: 'row';
|
|
|
|
tableId: string;
|
2020-10-19 14:25:21 +00:00
|
|
|
match: MatchSpec;
|
|
|
|
scope?: MatchSpec;
|
|
|
|
}
|
|
|
|
|
2020-11-03 23:44:09 +00:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
}
|
|
|
|
|
2020-10-19 14:25:21 +00:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
}
|
|
|
|
|
2020-11-03 23:44:09 +00:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
}
|
|
|
|
|
2020-10-19 14:25:21 +00:00
|
|
|
// 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;
|
2020-10-12 13:50:07 +00:00
|
|
|
colId: string;
|
2020-10-19 14:25:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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;
|
2020-10-12 13:50:07 +00:00
|
|
|
}
|