aboutsummaryrefslogtreecommitdiff
path: root/src/fields/util.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/fields/util.ts')
-rw-r--r--src/fields/util.ts96
1 files changed, 69 insertions, 27 deletions
diff --git a/src/fields/util.ts b/src/fields/util.ts
index a62795e64..957b2c8cd 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,5 +1,5 @@
import { UndoManager } from "../client/util/UndoManager";
-import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, CachedUpdates, DataSym, DocListCast, AclAdmin, FieldsSym, HeightSym, WidthSym } from "./Doc";
+import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, CachedUpdates, DataSym, DocListCast, AclAdmin, FieldsSym, HeightSym, WidthSym, fetchProto } from "./Doc";
import { SerializationHelper } from "../client/util/SerializationHelper";
import { ProxyField, PrefetchProxy } from "./Proxy";
import { RefField } from "./RefField";
@@ -10,6 +10,7 @@ import { DocServer } from "../client/DocServer";
import { ComputedField } from "./ScriptField";
import { ScriptCast, StrCast } from "./Types";
import { returnZero } from "../Utils";
+import { addSyntheticLeadingComment } from "typescript";
function _readOnlySetter(): never {
@@ -74,7 +75,7 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
const fromServer = target[UpdatingFromServer];
const sameAuthor = fromServer || (receiver.author === Doc.CurrentUserEmail);
const writeToDoc = sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (writeMode !== DocServer.WriteMode.LiveReadonly);
- const writeToServer = (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || writeMode === DocServer.WriteMode.Default) && !playgroundMode;
+ const writeToServer = (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || writeMode === DocServer.WriteMode.Default) && !DocServer.Control.isReadOnly();// && !playgroundMode;
if (writeToDoc) {
if (value === undefined) {
@@ -115,22 +116,34 @@ export function OVERRIDE_ACL(val: boolean) {
_overrideAcl = val;
}
-let playgroundMode = false;
+// playground mode allows the user to add/delete documents or make layout changes without them saving to the server
+// let playgroundMode = false;
-export function togglePlaygroundMode() {
- playgroundMode = !playgroundMode;
-}
-
-export function getPlaygroundMode() {
- return playgroundMode;
-}
+// export function togglePlaygroundMode() {
+// playgroundMode = !playgroundMode;
+// }
+// the list of groups that the current user is a member of
let currentUserGroups: string[] = [];
+// called from GroupManager once the groups have been fetched from the server
export function setGroups(groups: string[]) {
currentUserGroups = groups;
}
+/**
+ * These are the various levels of access a user can have to a document.
+ *
+ * Admin: a user with admin access to a document can remove/edit that document, add/remove/edit annotations (depending on permissions), as well as change others' access rights to that document.
+ *
+ * Edit: a user with edit access to a document can remove/edit that document, add/remove/edit annotations (depending on permissions), but not change any access rights to that document.
+ *
+ * Add: a user with add access to a document can add documents/annotations to that document but cannot edit or delete anything.
+ *
+ * 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.
+ */
export enum SharingPermissions {
Admin = "Admin",
Edit = "Can Edit",
@@ -139,18 +152,23 @@ export enum SharingPermissions {
None = "Not Shared"
}
+/**
+ * Calculates the effective access right to a document for the current user.
+ */
export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number): symbol {
+ if (!target) return AclPrivate;
if (in_prop === UpdatingFromServer || target[UpdatingFromServer]) return AclAdmin;
if (target[AclSym] && Object.keys(target[AclSym]).length) {
- if (target.__fields?.author === Doc.CurrentUserEmail || target.author === Doc.CurrentUserEmail || currentUserGroups.includes("admin")) return AclAdmin;
+ // if the current user is the author of the document / the current user is a member of the admin group
+ // but not if the doc in question is an alias - the current user will be the author of their alias rather than the original author
+ if ((Doc.CurrentUserEmail === (target.__fields?.author || target.author) && !(target.aliasOf || target.__fields?.aliasOf)) || currentUserGroups.includes("admin")) return AclAdmin;
+ // if the ACL is being overriden or the property being modified is one of the playground fields (which can be freely modified)
if (_overrideAcl || (in_prop && DocServer.PlaygroundFields?.includes(in_prop.toString()))) return AclEdit;
let effectiveAcl = AclPrivate;
- let aclPresent = false;
-
const HierarchyMapping = new Map<symbol, number>([
[AclPrivate, 0],
[AclReadonly, 1],
@@ -160,19 +178,28 @@ export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number)
]);
for (const [key, value] of Object.entries(target[AclSym])) {
+ // 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.
if (currentUserGroups.includes(key.substring(4)) || Doc.CurrentUserEmail === key.substring(4).replace("_", ".")) {
- if (HierarchyMapping.get(value as symbol)! >= HierarchyMapping.get(effectiveAcl)!) {
- aclPresent = true;
+ if (HierarchyMapping.get(value as symbol)! > HierarchyMapping.get(effectiveAcl)!) {
effectiveAcl = value as symbol;
- if (effectiveAcl === AclEdit) break;
+ if (effectiveAcl === AclAdmin) break;
}
}
}
- return aclPresent ? effectiveAcl : AclEdit;
+ // 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 AclAdmin;
}
-
+/**
+ * Recursively distributes the access right for a user across the children of a document and its annotations.
+ * @param key the key storing the access right (e.g. ACL-groupname)
+ * @param acl the access right being stored (e.g. "Can Edit")
+ * @param target the document on which this access right is being set
+ * @param inheritingFromCollection whether the target is being assigned rights after being dragged into a collection (and so is inheriting the ACLs from the collection)
+ * inheritingFromCollection is not currently being used but could be used if ACL assignment defaults change
+ */
export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, inheritingFromCollection?: boolean) {
const HierarchyMapping = new Map<string, number>([
@@ -183,37 +210,51 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
["Admin", 4]
]);
+ let changed = false; // determines whether fetchProto should be called or not (i.e. is there a change that should be reflected in target[AclSym])
const dataDoc = target[DataSym];
- if (!inheritingFromCollection || !target[key] || HierarchyMapping.get(StrCast(target[key]))! > HierarchyMapping.get(acl)!) target[key] = acl;
+ // 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 (!inheritingFromCollection || !target[key] || HierarchyMapping.get(StrCast(target[key]))! > HierarchyMapping.get(acl)!) {
+ target[key] = acl;
+ changed = true;
+
+ // maps over the aliases of the document
+ if (target.aliases) {
+ DocListCast(target.aliases).map(alias => {
+ distributeAcls(key, acl, alias);
+ });
+ }
+
+ }
if (dataDoc && (!inheritingFromCollection || !dataDoc[key] || HierarchyMapping.get(StrCast(dataDoc[key]))! > HierarchyMapping.get(acl)!)) {
dataDoc[key] = acl;
+ changed = true;
+ // maps over the children of the document
DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc)]).map(d => {
if (d.author === Doc.CurrentUserEmail && (!inheritingFromCollection || !d[key] || HierarchyMapping.get(StrCast(d[key]))! > HierarchyMapping.get(acl)!)) {
- distributeAcls(key, acl, d);
- d[key] = acl;
+ distributeAcls(key, acl, d, inheritingFromCollection);
}
const data = d[DataSym];
if (data && data.author === Doc.CurrentUserEmail && (!inheritingFromCollection || !data[key] || HierarchyMapping.get(StrCast(data[key]))! > HierarchyMapping.get(acl)!)) {
- distributeAcls(key, acl, data);
- data[key] = acl;
+ distributeAcls(key, acl, data, inheritingFromCollection);
}
});
+ // maps over the annotations of the document
DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + "-annotations"]).map(d => {
if (d.author === Doc.CurrentUserEmail && (!inheritingFromCollection || !d[key] || HierarchyMapping.get(StrCast(d[key]))! > HierarchyMapping.get(acl)!)) {
- distributeAcls(key, acl, d);
- d[key] = acl;
+ distributeAcls(key, acl, d, inheritingFromCollection);
}
const data = d[DataSym];
if (data && data.author === Doc.CurrentUserEmail && (!inheritingFromCollection || !data[key] || HierarchyMapping.get(StrCast(data[key]))! > HierarchyMapping.get(acl)!)) {
- distributeAcls(key, acl, data);
- data[key] = acl;
+ distributeAcls(key, acl, data, inheritingFromCollection);
}
});
}
+
+ changed && fetchProto(target); // updates target[AclSym] when changes to acls have been made
}
const layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHeight", "fitWidth", "fitToBox",
@@ -223,6 +264,7 @@ export function setter(target: any, in_prop: string | symbol | number, value: an
const effectiveAcl = GetEffectiveAcl(target, in_prop);
if (effectiveAcl !== AclEdit && effectiveAcl !== AclAdmin) 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].includes(value))) return true;
// if (typeof prop === "string" && prop.startsWith("ACL") && !["Can Edit", "Can Add", "Can View", "Not Shared", undefined].includes(value)) return true;