diff options
Diffstat (limited to 'src/fields/util.ts')
-rw-r--r-- | src/fields/util.ts | 110 |
1 files changed, 43 insertions, 67 deletions
diff --git a/src/fields/util.ts b/src/fields/util.ts index 7f4892bd6..51c76b19a 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -8,13 +8,11 @@ import { returnZero } from '../Utils'; import CursorField from './CursorField'; import { AclAdmin, - AclAugment, AclEdit, + aclLevel, AclPrivate, - AclReadonly, AclSelfEdit, AclSym, - AclUnset, DataSym, Doc, DocListCast, @@ -22,8 +20,10 @@ import { FieldResult, ForceServerWrite, HeightSym, + HierarchyMapping, Initializing, LayoutSym, + ReverseHierarchyMap, updateCachedAcls, UpdatingFromServer, WidthSym, @@ -118,6 +118,7 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number if (writeToServer) { if (value === undefined) target[Update]({ $unset: { ['fields.' + prop]: '' } }); else target[Update]({ $set: { ['fields.' + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : value === undefined ? null : value } }); + if (prop === 'author' || prop.toString().startsWith('acl')) updateCachedAcls(target); } else { DocServer.registerDocWithCachedUpdate(receiver, prop as string, curValue); } @@ -125,7 +126,12 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number (!receiver[UpdatingFromServer] || receiver[ForceServerWrite]) && UndoManager.AddEvent({ redo: () => (receiver[prop] = value), - undo: () => (receiver[prop] = curValue), + undo: () => { + const wasUpdate = receiver[UpdatingFromServer]; + receiver[UpdatingFromServer] = true; // needed if the event caused ACL's to change such that the doc is otherwise no longer editable. + receiver[prop] = curValue; + receiver[UpdatingFromServer] = wasUpdate; + }, prop: prop?.toString(), }); return true; @@ -182,8 +188,11 @@ export function inheritParentAcls(parent: Doc, child: Doc) { * View: a user with view access to a document can only view it - they cannot add/remove/edit anything. * * None: the document is not shared with that user. + * + * Unset: Remove a sharing permission (eg., used ) */ export enum SharingPermissions { + Unset = 'None', Admin = 'Admin', Edit = 'Edit', SelfEdit = 'Self Edit', @@ -203,22 +212,16 @@ const getEffectiveAclCache = computedFn(function (target: any, user?: string) { export function GetEffectiveAcl(target: any, user?: string): symbol { if (!target) return AclPrivate; if (target[UpdatingFromServer]) return AclAdmin; - // authored documents are private until an ACL is set. - if (!target[AclSym] && target.author && target.author !== Doc.CurrentUserEmail) return AclPrivate; return getEffectiveAclCache(target, user); // all changes received from the server must be processed as Admin. return this directly so that the acls aren't cached (UpdatingFromServer is not observable) } function getPropAcl(target: any, prop: string | symbol | number) { - if (prop === UpdatingFromServer || prop === Initializing || target[UpdatingFromServer] || prop === AclSym) return AclAdmin; // requesting the UpdatingFromServer prop or AclSym must always go through to keep the local DB consistent + if (typeof prop === 'symbol' || target[UpdatingFromServer]) return AclAdmin; // requesting the UpdatingFromServer prop or AclSym must always go through to keep the local DB consistent if (prop && DocServer.IsPlaygroundField(prop.toString())) return AclEdit; // playground props are always editable return GetEffectiveAcl(target); } -let HierarchyMapping: Map<symbol, number> | undefined; - let cachedGroups = observable([] as string[]); -/// bcz; argh!! TODO; These do not belong here, but there were include order problems with leaving them in util.ts -// need to investigate further what caused the mobx update problems and move to a better location. const getCachedGroupByNameCache = computedFn(function (name: string) { return cachedGroups.includes(name); }, true); @@ -230,42 +233,32 @@ export function SetCachedGroups(groups: string[]) { } function getEffectiveAcl(target: any, user?: string): symbol { const targetAcls = target[AclSym]; - const userChecked = user || Doc.CurrentUserEmail; // if the current user is the author of the document / the current user is a member of the admin group - const targetAuthor = target.__fields?.author || target.author; // target may be a Doc of Proxy, so check __fields.author and .author - if (userChecked === targetAuthor || !targetAuthor) return AclAdmin; - if (GetCachedGroupByName('Admin')) return AclAdmin; + if (targetAcls?.['acl-Me'] === AclAdmin || GetCachedGroupByName('Admin')) return AclAdmin; + const userChecked = user || Doc.CurrentUserEmail; // if the current user is the author of the document / the current user is a member of the admin group if (targetAcls && Object.keys(targetAcls).length) { - HierarchyMapping = - HierarchyMapping || - new Map<symbol, number>([ - [AclPrivate, 0], - [AclReadonly, 1], - [AclAugment, 2], - [AclSelfEdit, 2.5], - [AclEdit, 3], - [AclAdmin, 4], - ]); - let effectiveAcl = AclPrivate; for (const [key, value] of Object.entries(targetAcls)) { // there are issues with storing fields with . in the name, so they are replaced with _ during creation // as a result we need to restore them again during this comparison. const entity = denormalizeEmail(key.substring(4)); // an individual or a group - if (HierarchyMapping.get(value as symbol)! > HierarchyMapping.get(effectiveAcl)!) { - if (GetCachedGroupByName(entity) || userChecked === entity) { + if (HierarchyMapping.get(value as symbol)!.level > HierarchyMapping.get(effectiveAcl)!.level) { + if (GetCachedGroupByName(entity) || userChecked === entity || entity === 'Me') { effectiveAcl = value as symbol; } } } // if there's an overriding acl set through the properties panel or sharing menu, that's what's returned if the user isn't an admin of the document - const override = targetAcls['acl-Override']; - if (override !== AclUnset && override !== undefined) effectiveAcl = override; + //const override = targetAcls['acl-Override']; + // if (override !== AclUnset && override !== undefined) effectiveAcl = override; // if we're in playground mode, return AclEdit (or AclAdmin if that's the user's effectiveAcl) - return DocServer?.Control?.isReadOnly?.() && HierarchyMapping.get(effectiveAcl)! < 3 ? AclEdit : effectiveAcl; + return DocServer?.Control?.isReadOnly?.() && HierarchyMapping.get(effectiveAcl)!.level < aclLevel.editable ? AclEdit : effectiveAcl; } + // authored documents are private until an ACL is set. + const targetAuthor = target.__fields?.author || target.author; // target may be a Doc of Proxy, so check __fields.author and .author + if (targetAuthor && targetAuthor !== userChecked) return AclPrivate; return AclAdmin; } /** @@ -290,21 +283,9 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc } visited.push(target); - const HierarchyMapping = new Map<string, number>([ - ['Not Shared', 0], - ['Can View', 1], - ['Can Augment', 2], - ['Self Edit', 2.5], - ['Can Edit', 3], - ['Admin', 4], - ]); - let layoutDocChanged = false; // determines whether fetchProto should be called or not (i.e. is there a change that should be reflected in target[AclSym]) - let dataDocChanged = false; - const dataDoc = target[DataSym]; - // if it is inheriting from a collection, it only inherits if A) the key doesn't already exist or B) the right being inherited is more restrictive - if (GetEffectiveAcl(target) === AclAdmin && (!inheritingFromCollection || !target[key] || HierarchyMapping.get(StrCast(target[key]))! > HierarchyMapping.get(acl)!)) { + if (GetEffectiveAcl(target) === AclAdmin && (!inheritingFromCollection || !target[key] || ReverseHierarchyMap.get(StrCast(target[key]))!.level > ReverseHierarchyMap.get(acl)!.level)) { target[key] = acl; layoutDocChanged = true; @@ -315,15 +296,16 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc } } - if (dataDoc && (!inheritingFromCollection || !dataDoc[key] || HierarchyMapping.get(StrCast(dataDoc[key]))! > HierarchyMapping.get(acl)!)) { + let dataDocChanged = false; + const dataDoc = target[DataSym]; + if (dataDoc && (!inheritingFromCollection || !dataDoc[key] || ReverseHierarchyMap.get(StrCast(dataDoc[key]))! > ReverseHierarchyMap.get(acl)!)) { if (GetEffectiveAcl(dataDoc) === AclAdmin) { dataDoc[key] = acl; dataDocChanged = true; } // maps over the links of the document - const links = DocListCast(dataDoc.links); - links.forEach(link => distributeAcls(key, acl, link, inheritingFromCollection, visited)); + DocListCast(dataDoc.links).forEach(link => distributeAcls(key, acl, link, inheritingFromCollection, visited)); // maps over the children of the document DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + (isDashboard ? '-all' : '')]).map(d => { @@ -352,10 +334,10 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean { let prop = in_prop; - const effectiveAcl = getPropAcl(target, prop); + const effectiveAcl = in_prop === 'constructor' || typeof in_prop === 'symbol' ? AclAdmin : getPropAcl(target, prop); if (effectiveAcl !== AclEdit && effectiveAcl !== AclAdmin && !(effectiveAcl === AclSelfEdit && value instanceof RichTextField)) return true; // if you're trying to change an acl but don't have Admin access / you're trying to change it to something that isn't an acceptable acl, you can't - if (typeof prop === 'string' && prop.startsWith('acl') && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined, 'None'].includes(value))) return true; + if (typeof prop === 'string' && prop.startsWith('acl') && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined].includes(value))) return true; // if (typeof prop === "string" && prop.startsWith("acl") && !["Can Edit", "Can Augment", "Can View", "Not Shared", undefined].includes(value)) return true; if (typeof prop === 'string' && prop !== '__id' && prop !== '__fields' && prop.startsWith('_')) { @@ -371,27 +353,21 @@ export function setter(target: any, in_prop: string | symbol | number, value: an return _setter(target, prop, value, receiver); } -export function getter(target: any, in_prop: string | symbol | number, receiver: any): any { - let prop = in_prop; - +export function getter(target: any, in_prop: string | symbol, receiver: any): any { + if (in_prop === 'constructor' || in_prop === 'toString' || in_prop === 'valueOf' || in_prop === 'factory' || in_prop === 'serializeInfo') return target[in_prop]; + if (in_prop === 'then') return undefined; //If we're being awaited if (in_prop === AclSym) return target[AclSym]; - if (in_prop === 'toString' || (in_prop !== HeightSym && in_prop !== WidthSym && in_prop !== LayoutSym && typeof prop === 'symbol')) return target.__fields[prop] || target[prop]; - if (GetEffectiveAcl(target) === AclPrivate) return prop === HeightSym || prop === WidthSym ? returnZero : undefined; - if (prop === LayoutSym) return target.__LAYOUT__; - if (typeof prop === 'string' && prop !== '__id' && prop !== '__fields' && prop.startsWith('_')) { - if (!prop.startsWith('__')) prop = prop.substring(1); - if (target.__LAYOUT__) return target.__LAYOUT__[prop]; - } - if (prop === 'then') { - //If we're being awaited + if (in_prop === LayoutSym) return target.__LAYOUT__; + if ((in_prop === HeightSym || in_prop === WidthSym) && GetEffectiveAcl(target) === AclPrivate) return returnZero; + if (typeof in_prop === 'symbol' || in_prop.startsWith('isMobX') || in_prop.startsWith('__')) return target.__fields[in_prop] || target[in_prop]; + if (GetEffectiveAcl(target) === AclPrivate) { + if (in_prop === 'author') return target.__fields[in_prop] || target[in_prop]; return undefined; } - if (typeof prop === 'symbol') { - return target.__fields[prop] || target[prop]; - } - if (SerializationHelper.IsSerializing()) { - return target[prop]; - } + + const prop = in_prop.startsWith('_') ? in_prop.substring(1) : in_prop; + if (prop !== in_prop && target.__LAYOUT__) return target.__LAYOUT__[prop]; + if (SerializationHelper.IsSerializing()) return target[prop]; return getFieldImpl(target, prop, receiver); } |