(core) Showing censored values as a grey cell

Test Plan: Block read access to column A based on the condition rec.B == 1. Then setting B = 1 in a row makes the cell under A grey.

Reviewers: dsagal

Reviewed By: dsagal

Subscribers: paulfitz, dsagal

Differential Revision: https://phab.getgrist.com/D2828
pull/9/head
Alex Hall 3 years ago
parent 2feef7f780
commit 2f3a0e0c7f

@ -78,6 +78,27 @@
background-color: unset;
}
.field_clip.invalid.field-error-C {
background-color: unset;
color: var(--grist-color-dark-grey);
padding-left: 18px;
}
.field_clip.invalid.field-error-C::before {
/* based on standard icon styles */
content: "";
position: absolute;
top: 4px;
left: 2px;
width: 14px;
height: 14px;
background-color: var(--grist-color-dark-grey);
-webkit-mask-repeat: no-repeat;
-webkit-mask-position: center;
-webkit-mask-size: contain;
-webkit-mask-image: var(--icon-Lock);
}
.field_clip.field-error-U {
color: #6363a2;
background-color: unset;

@ -152,7 +152,19 @@ export class FieldEditor extends Disposable {
const column = this._field.column();
const cellCurrentValue = this._editRow.cells[this._field.colId()].peek();
const cellValue = column.isFormula() ? column.formula() : cellCurrentValue;
let cellValue: CellValue;
if (column.isFormula()) {
cellValue = column.formula();
} else if (Array.isArray(cellCurrentValue) && cellCurrentValue[0] === 'C') {
// This cell value is censored by access control rules
// Really the rules should also block editing, but in case they don't, show a blank value
// rather than a 'C'. However if the user tries to edit the cell and then clicks away
// without typing anything the empty string is saved, deleting what was there.
// We should probably just automatically block updates where reading is not allowed.
cellValue = '';
} else {
cellValue = cellCurrentValue;
}
// Enter formula-editing mode (e.g. click-on-column inserts its ID) only if we are opening the
// editor by typing into it (and overriding previous formula). In other cases (e.g. double-click),

@ -109,6 +109,19 @@ export class SkipValue {
}
}
/**
* A placeholder for a value hidden by access control rules.
* Depending on the types of the columns involved, copying
* a censored value and pasting elsewhere will either use
* CensoredValue.__repr__ (python) or CensoredValue.toString (typescript)
* so they should match
*/
export class CensoredValue {
public toString() {
return 'CENSORED';
}
}
/**
* Produces a Grist-encoded version of the value, e.g. turning a Date into ['d', timestamp].
* Returns ['U', repr(value)] if it fails to encode otherwise.
@ -135,6 +148,8 @@ export function encodeObject(value: unknown): CellValue {
// TODO Depending on how it's used, may want to return ['d', timestamp] for UTC midnight.
return ['D', timestamp, 'UTC'];
}
} else if (value instanceof CensoredValue) {
return ['C'];
} else if (value instanceof RaisedException) {
return ['E', value.name, value.message, value.details];
} else if (Array.isArray(value)) {
@ -172,6 +187,7 @@ export function decodeObject(value: CellValue): unknown {
case 'P': return new PendingValue();
case 'R': return new Reference(String(args[0]), args[1]);
case 'S': return new SkipValue();
case 'C': return new CensoredValue();
case 'U': return new UnknownValue(args[0]);
}
} catch (e) {

@ -888,9 +888,9 @@ export class GranularAccess implements GranularAccessForBundle {
if (colValues === undefined) {
censorAt = () => 1;
} else if (Array.isArray(action[2])) {
censorAt = (colId, idx) => (colValues as BulkColValues)[colId][idx] = 'CENSORED'; // TODO Pick a suitable value
censorAt = (colId, idx) => (colValues as BulkColValues)[colId][idx] = ['C']; // censored
} else {
censorAt = (colId) => (colValues as ColValues)[colId] = 'CENSORED'; // TODO Pick a suitable value
censorAt = (colId) => (colValues as ColValues)[colId] = ['C']; // censored
}
// These map an index of a row in the action to its index in rowsBefore and in rowsAfter.

@ -105,6 +105,17 @@ class UnmarshallableValue(object):
# document was just migrated.
_pending_sentinel = object()
# A placeholder for a value hidden by access control rules.
# Depending on the types of the columns involved, copying
# a censored value and pasting elsewhere will either use
# CensoredValue.__repr__ (python) or CensoredValue.toString (typescript)
# so they should match
class CensoredValue(object):
def __repr__(self):
return 'CENSORED'
_censored_sentinel = CensoredValue()
_max_js_int = 1<<31
@ -178,6 +189,8 @@ def encode_object(value):
return ['O', {key: encode_object(val) for key, val in value.iteritems()}]
elif value == _pending_sentinel:
return ['P']
elif value == _censored_sentinel:
return ['C']
elif isinstance(value, UnmarshallableValue):
return ['U', value.value_repr]
except Exception as e:
@ -218,6 +231,8 @@ def decode_object(value):
return {decode_object(key): decode_object(val) for key, val in args[0].iteritems()}
elif code == 'P':
return _pending_sentinel
elif code == 'C':
return _censored_sentinel
elif code == 'U':
return UnmarshallableValue(args[0])
raise KeyError("Unknown object type code %r" % code)

Loading…
Cancel
Save