Files
outline/server/policies/document.ts
Apoorv Mishra 1490c3a14b Individual document sharing with permissions (#5814)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Tom Moor <tom@getoutline.com>
2024-01-30 17:48:22 -08:00

482 lines
10 KiB
TypeScript

import invariant from "invariant";
import some from "lodash/some";
import {
CollectionPermission,
DocumentPermission,
TeamPreference,
} from "@shared/types";
import { Document, Revision, User, Team } from "@server/models";
import { allow, _cannot as cannot, _can as can } from "./cancan";
allow(User, "createDocument", Team, (user, team) => {
if (!team || user.isViewer || user.teamId !== team.id) {
return false;
}
return true;
});
allow(User, "read", Document, (user, document) => {
if (!document) {
return false;
}
if (
includesMembership(document, [
DocumentPermission.Read,
DocumentPermission.ReadWrite,
])
) {
return true;
}
// existence of collection option is not required here to account for share tokens
if (
document.collection &&
cannot(user, "readDocument", document.collection)
) {
return false;
}
if (document.isDraft) {
return user.id === document.createdById;
}
return user.teamId === document.teamId;
});
allow(User, "download", Document, (user, document) => {
if (!document) {
return false;
}
if (
user.isViewer &&
!user.team.getPreference(TeamPreference.ViewersCanExport)
) {
return false;
}
if (
includesMembership(document, [
DocumentPermission.Read,
DocumentPermission.ReadWrite,
])
) {
return true;
}
// existence of collection option is not required here to account for share tokens
if (
document.collection &&
cannot(user, "readDocument", document.collection)
) {
return false;
}
if (document.isDraft) {
return user.id === document.createdById;
}
return user.teamId === document.teamId;
});
allow(User, "comment", Document, (user, document) => {
if (!document || !document.isActive || document.template) {
return false;
}
if (
includesMembership(document, [
DocumentPermission.Read,
DocumentPermission.ReadWrite,
])
) {
return true;
}
if (document.collectionId) {
invariant(
document.collection,
"collection is missing, did you forget to include in the query scope?"
);
if (can(user, "readDocument", document.collection)) {
return true;
}
}
return user.id === document.createdById;
});
allow(User, ["star", "unstar"], Document, (user, document) => {
if (!document || !document.isActive || document.template) {
return false;
}
if (
includesMembership(document, [
DocumentPermission.Read,
DocumentPermission.ReadWrite,
])
) {
return true;
}
if (document.collectionId) {
invariant(
document.collection,
"collection is missing, did you forget to include in the query scope?"
);
if (cannot(user, "readDocument", document.collection)) {
return false;
}
}
return user.teamId === document.teamId;
});
allow(User, "share", Document, (user, document) => {
if (
!document ||
document.archivedAt ||
document.deletedAt ||
document.template
) {
return false;
}
if (document.collectionId) {
invariant(
document.collection,
"collection is missing, did you forget to include in the query scope?"
);
if (cannot(user, "share", document.collection)) {
return false;
}
}
if (document.isDraft) {
return user.id === document.createdById;
}
return user.teamId === document.teamId;
});
allow(User, "update", Document, (user, document) => {
if (!document || !document.isActive) {
return false;
}
if (includesMembership(document, [DocumentPermission.ReadWrite])) {
return true;
}
if (document.collectionId) {
invariant(
document.collection,
"collection is missing, did you forget to include in the query scope?"
);
if (cannot(user, "updateDocument", document.collection)) {
return false;
}
}
if (document.isDraft) {
return user.id === document.createdById;
}
return user.teamId === document.teamId;
});
allow(User, "publish", Document, (user, document) => {
if (!document || !document.isActive || !document.isDraft) {
return false;
}
if (document.collectionId) {
invariant(
document.collection,
"collection is missing, did you forget to include in the query scope?"
);
if (can(user, "updateDocument", document.collection)) {
return true;
}
}
return user.id === document.createdById;
});
allow(User, ["manageUsers", "duplicate"], Document, (user, document) => {
if (!document || !document.isActive) {
return false;
}
if (document.collectionId) {
invariant(
document.collection,
"collection is missing, did you forget to include in the query scope?"
);
if (can(user, "updateDocument", document.collection)) {
return true;
}
}
return user.id === document.createdById;
});
allow(User, "updateInsights", Document, (user, document) => {
if (!document || !document.isActive) {
return false;
}
if (document.collectionId) {
invariant(
document.collection,
"collection is missing, did you forget to include in the query scope?"
);
if (can(user, "update", document.collection)) {
return true;
}
}
return user.id === document.createdById;
});
allow(User, "createChildDocument", Document, (user, document) => {
if (
!document ||
!document.isActive ||
document.isDraft ||
document.template
) {
return false;
}
invariant(
document.collection,
"collection is missing, did you forget to include in the query scope?"
);
if (can(user, "updateDocument", document.collection)) {
return true;
}
return user.id === document.createdById;
});
allow(User, "move", Document, (user, document) => {
if (!document || !document.isActive) {
return false;
}
if (document.collection && can(user, "updateDocument", document.collection)) {
return true;
}
return user.id === document.createdById;
});
allow(User, "pin", Document, (user, document) => {
if (
!document ||
!document.isActive ||
document.isDraft ||
document.template
) {
return false;
}
invariant(
document.collection,
"collection is missing, did you forget to include in the query scope?"
);
if (can(user, "update", document.collection)) {
return true;
}
return user.id === document.createdById;
});
allow(User, "unpin", Document, (user, document) => {
if (!document || document.isDraft || document.template) {
return false;
}
invariant(
document.collection,
"collection is missing, did you forget to include in the query scope?"
);
if (can(user, "update", document.collection)) {
return true;
}
return user.id === document.createdById;
});
allow(User, ["subscribe", "unsubscribe"], Document, (user, document) => {
if (
!document ||
!document.isActive ||
document.isDraft ||
document.template
) {
return false;
}
if (
includesMembership(document, [
DocumentPermission.Read,
DocumentPermission.ReadWrite,
])
) {
return true;
}
invariant(
document.collection,
"collection is missing, did you forget to include in the query scope?"
);
if (can(user, "readDocument", document.collection)) {
return true;
}
return user.id === document.createdById;
});
allow(User, "pinToHome", Document, (user, document) => {
if (
!document ||
!document.isActive ||
document.isDraft ||
document.template
) {
return false;
}
return user.teamId === document.teamId && user.isAdmin;
});
allow(User, "delete", Document, (user, document) => {
if (!document || document.deletedAt || user.isViewer) {
return false;
}
// allow deleting document without a collection
if (
document.collection &&
cannot(user, "deleteDocument", document.collection)
) {
return false;
}
// unpublished drafts can always be deleted by their owner
if (document.isDraft) {
return user.id === document.createdById;
}
return user.teamId === document.teamId;
});
allow(User, "permanentDelete", Document, (user, document) => {
if (!document || !document.deletedAt || user.isViewer) {
return false;
}
// allow deleting document without a collection
if (
document.collection &&
cannot(user, "updateDocument", document.collection)
) {
return false;
}
// unpublished drafts can always be deleted by their owner
if (document.isDraft && user.id === document.createdById) {
return true;
}
return user.teamId === document.teamId && user.isAdmin;
});
allow(User, "restore", Document, (user, document) => {
if (!document || !document.deletedAt) {
return false;
}
if (
document.collection &&
cannot(user, "updateDocument", document.collection)
) {
return false;
}
// unpublished drafts can always be restored by their owner
if (document.isDraft && user.id === document.createdById) {
return true;
}
return user.teamId === document.teamId;
});
allow(User, "archive", Document, (user, document) => {
if (
!document ||
!document.isActive ||
document.isDraft ||
document.template
) {
return false;
}
invariant(
document.collection,
"collection is missing, did you forget to include in the query scope?"
);
if (cannot(user, "updateDocument", document.collection)) {
return false;
}
return user.teamId === document.teamId;
});
allow(User, "unarchive", Document, (user, document) => {
if (!document || !document.archivedAt || document.deletedAt) {
return false;
}
invariant(
document.collection,
"collection is missing, did you forget to include in the query scope?"
);
if (cannot(user, "updateDocument", document.collection)) {
return false;
}
if (document.isDraft) {
return user.id === document.createdById;
}
return user.teamId === document.teamId;
});
allow(
Document,
"restore",
Revision,
(document, revision) => document.id === revision?.documentId
);
allow(User, "unpublish", Document, (user, document) => {
if (!document || !document.isActive || document.isDraft || user.isViewer) {
return false;
}
invariant(
document.collection,
"collection is missing, did you forget to include in the query scope?"
);
if (cannot(user, "updateDocument", document.collection)) {
return false;
}
return user.teamId === document.teamId;
});
function includesMembership(
document: Document,
permissions: (DocumentPermission | CollectionPermission)[]
) {
invariant(
document.memberships,
"document memberships should be preloaded, did you forget withMembership scope?"
);
return some(document.memberships, (m) => permissions.includes(m.permission));
}