feat: Add read-only collections (#1991)

closes #1017
This commit is contained in:
Tom Moor
2021-03-30 21:02:08 -07:00
committed by GitHub
parent d7acf616cf
commit 7e1b07ef98
50 changed files with 940 additions and 558 deletions

View File

@@ -112,7 +112,7 @@ describe("#attachments.delete", () => {
it("should not allow deleting an attachment belonging to a document user does not have access to", async () => {
const user = await buildUser();
const collection = await buildCollection({
private: true,
permission: null,
});
const document = await buildDocument({
teamId: collection.teamId,
@@ -184,7 +184,7 @@ describe("#attachments.redirect", () => {
const collection = await buildCollection({
teamId: user.teamId,
userId: user.id,
private: true,
permission: null,
});
const document = await buildDocument({
teamId: user.teamId,
@@ -208,7 +208,7 @@ describe("#attachments.redirect", () => {
it("should not return a redirect for a private attachment belonging to a document user does not have access to", async () => {
const user = await buildUser();
const collection = await buildCollection({
private: true,
permission: null,
});
const document = await buildDocument({
teamId: collection.teamId,

View File

@@ -39,13 +39,13 @@ router.post("collections.create", auth(), async (ctx) => {
name,
color,
description,
permission,
sharing,
icon,
sort = Collection.DEFAULT_SORT,
} = ctx.body;
let { index } = ctx.body;
const isPrivate = ctx.body.private;
ctx.assertPresent(name, "name is required");
if (color) {
@@ -89,7 +89,7 @@ router.post("collections.create", auth(), async (ctx) => {
color,
teamId: user.teamId,
createdById: user.id,
private: isPrivate,
permission: permission ? permission : null,
sharing,
sort,
index,
@@ -105,11 +105,9 @@ router.post("collections.create", auth(), async (ctx) => {
});
// we must reload the collection to get memberships for policy presenter
if (isPrivate) {
collection = await Collection.scope({
method: ["withMembership", user.id],
}).findByPk(collection.id);
}
collection = await Collection.scope({
method: ["withMembership", user.id],
}).findByPk(collection.id);
ctx.body = {
data: presentCollection(collection),
@@ -514,8 +512,16 @@ router.post("collections.export_all", auth(), async (ctx) => {
});
router.post("collections.update", auth(), async (ctx) => {
let { id, name, description, icon, color, sort, sharing } = ctx.body;
const isPrivate = ctx.body.private;
let {
id,
name,
description,
icon,
permission,
color,
sort,
sharing,
} = ctx.body;
if (color) {
ctx.assertHexColor(color, "Invalid hex value (please use format #FFFFFF)");
@@ -528,9 +534,9 @@ router.post("collections.update", auth(), async (ctx) => {
authorize(user, "update", collection);
// we're making this collection private right now, ensure that the current
// we're making this collection have no default access, ensure that the current
// user has a read-write membership so that at least they can edit it
if (isPrivate && !collection.private) {
if (permission !== "read_write" && collection.permission === "read_write") {
await CollectionUser.findOrCreate({
where: {
collectionId: collection.id,
@@ -543,7 +549,7 @@ router.post("collections.update", auth(), async (ctx) => {
});
}
const isPrivacyChanged = isPrivate !== collection.private;
const permissionChanged = permission !== collection.permission;
if (name !== undefined) {
collection.name = name;
@@ -557,8 +563,8 @@ router.post("collections.update", auth(), async (ctx) => {
if (color !== undefined) {
collection.color = color;
}
if (isPrivate !== undefined) {
collection.private = isPrivate;
if (permission !== undefined) {
collection.permission = permission ? permission : null;
}
if (sharing !== undefined) {
collection.sharing = sharing;
@@ -580,7 +586,7 @@ router.post("collections.update", auth(), async (ctx) => {
// must reload to update collection membership for correct policy calculation
// if the privacy level has changed. Otherwise skip this query for speed.
if (isPrivacyChanged) {
if (permissionChanged) {
await collection.reload();
}

View File

@@ -42,7 +42,7 @@ describe("#collections.list", () => {
it("should not return private collections actor is not a member of", async () => {
const { user, collection } = await seed();
await buildCollection({
private: true,
permission: null,
teamId: user.teamId,
});
const res = await server.post("/api/collections.list", {
@@ -58,12 +58,12 @@ describe("#collections.list", () => {
it("should return private collections actor is a member of", async () => {
const user = await buildUser();
await buildCollection({
private: true,
permission: null,
teamId: user.teamId,
userId: user.id,
});
await buildCollection({
private: true,
permission: null,
teamId: user.teamId,
userId: user.id,
});
@@ -82,13 +82,13 @@ describe("#collections.list", () => {
it("should return private collections actor is a group-member of", async () => {
const user = await buildUser();
await buildCollection({
private: true,
permission: null,
teamId: user.teamId,
userId: user.id,
});
const collection = await buildCollection({
private: true,
permission: null,
teamId: user.teamId,
});
@@ -256,7 +256,7 @@ describe("#collections.export", () => {
it("should now allow export of private collection not a member", async () => {
const { user } = await seed();
const collection = await buildCollection({
private: true,
permission: null,
teamId: user.teamId,
});
const res = await server.post("/api/collections.export", {
@@ -268,7 +268,7 @@ describe("#collections.export", () => {
it("should allow export of private collection when the actor is a member", async () => {
const { user, collection } = await seed();
collection.private = true;
collection.permission = null;
await collection.save();
await CollectionUser.create({
@@ -288,7 +288,7 @@ describe("#collections.export", () => {
it("should allow export of private collection when the actor is a group member", async () => {
const user = await buildUser();
const collection = await buildCollection({
private: true,
permission: null,
teamId: user.teamId,
});
@@ -369,7 +369,7 @@ describe("#collections.add_user", () => {
const collection = await buildCollection({
teamId: user.teamId,
userId: user.id,
private: true,
permission: null,
});
const anotherUser = await buildUser({ teamId: user.teamId });
const res = await server.post("/api/collections.add_user", {
@@ -389,7 +389,7 @@ describe("#collections.add_user", () => {
const user = await buildUser();
const collection = await buildCollection({
teamId: user.teamId,
private: true,
permission: null,
});
const anotherUser = await buildUser();
const res = await server.post("/api/collections.add_user", {
@@ -433,7 +433,7 @@ describe("#collections.add_group", () => {
const collection = await buildCollection({
teamId: user.teamId,
userId: user.id,
private: true,
permission: null,
});
const group = await buildGroup({ teamId: user.teamId });
const res = await server.post("/api/collections.add_group", {
@@ -454,7 +454,7 @@ describe("#collections.add_group", () => {
const collection = await buildCollection({
teamId: user.teamId,
userId: user.id,
private: true,
permission: null,
});
const group = await buildGroup();
const res = await server.post("/api/collections.add_group", {
@@ -496,7 +496,7 @@ describe("#collections.remove_group", () => {
const collection = await buildCollection({
teamId: user.teamId,
userId: user.id,
private: true,
permission: null,
});
const group = await buildGroup({ teamId: user.teamId });
@@ -528,7 +528,7 @@ describe("#collections.remove_group", () => {
const user = await buildUser();
const collection = await buildCollection({
teamId: user.teamId,
private: true,
permission: null,
});
const group = await buildGroup();
const res = await server.post("/api/collections.remove_group", {
@@ -572,7 +572,7 @@ describe("#collections.remove_user", () => {
const collection = await buildCollection({
teamId: user.teamId,
userId: user.id,
private: true,
permission: null,
});
const anotherUser = await buildUser({ teamId: user.teamId });
@@ -601,7 +601,7 @@ describe("#collections.remove_user", () => {
const user = await buildUser();
const collection = await buildCollection({
teamId: user.teamId,
private: true,
permission: null,
});
const anotherUser = await buildUser();
const res = await server.post("/api/collections.remove_user", {
@@ -642,7 +642,7 @@ describe("#collections.remove_user", () => {
describe("#collections.users", () => {
it("should return users in private collection", async () => {
const { collection, user } = await seed();
collection.private = true;
collection.permission = null;
await collection.save();
await CollectionUser.create({
@@ -684,7 +684,7 @@ describe("#collections.group_memberships", () => {
const user = await buildUser();
const group = await buildGroup({ teamId: user.teamId });
const collection = await buildCollection({
private: true,
permission: null,
teamId: user.teamId,
});
@@ -721,7 +721,7 @@ describe("#collections.group_memberships", () => {
const group = await buildGroup({ name: "will find", teamId: user.teamId });
const group2 = await buildGroup({ name: "wont find", teamId: user.teamId });
const collection = await buildCollection({
private: true,
permission: null,
teamId: user.teamId,
});
@@ -766,7 +766,7 @@ describe("#collections.group_memberships", () => {
const group = await buildGroup({ teamId: user.teamId });
const group2 = await buildGroup({ teamId: user.teamId });
const collection = await buildCollection({
private: true,
permission: null,
teamId: user.teamId,
});
@@ -817,7 +817,7 @@ describe("#collections.group_memberships", () => {
it("should require authorization", async () => {
const user = await buildUser();
const collection = await buildCollection({
private: true,
permission: null,
teamId: user.teamId,
});
@@ -831,7 +831,7 @@ describe("#collections.group_memberships", () => {
describe("#collections.memberships", () => {
it("should return members in private collection", async () => {
const { collection, user } = await seed();
collection.private = true;
collection.permission = null;
await collection.save();
await CollectionUser.create({
@@ -945,7 +945,7 @@ describe("#collections.info", () => {
it("should require user member of collection", async () => {
const { user, collection } = await seed();
collection.private = true;
collection.permission = null;
await collection.save();
const res = await server.post("/api/collections.info", {
@@ -956,7 +956,7 @@ describe("#collections.info", () => {
it("should allow user member of collection", async () => {
const { user, collection } = await seed();
collection.private = true;
collection.permission = null;
await collection.save();
await CollectionUser.create({
@@ -1034,12 +1034,12 @@ describe("#collections.create", () => {
it("should return correct policies with private collection", async () => {
const { user } = await seed();
const res = await server.post("/api/collections.create", {
body: { token: user.getJwtToken(), name: "Test", private: true },
body: { token: user.getJwtToken(), name: "Test", permission: null },
});
const body = await res.json();
expect(res.status).toEqual(200);
expect(body.data.private).toBeTruthy();
expect(body.data.permission).toEqual(null);
expect(body.policies.length).toBe(1);
expect(body.policies[0].abilities.read).toBeTruthy();
expect(body.policies[0].abilities.export).toBeTruthy();
@@ -1176,11 +1176,11 @@ describe("#collections.update", () => {
it("allows editing individual fields", async () => {
const { user, collection } = await seed();
const res = await server.post("/api/collections.update", {
body: { token: user.getJwtToken(), id: collection.id, private: true },
body: { token: user.getJwtToken(), id: collection.id, permission: null },
});
const body = await res.json();
expect(res.status).toEqual(200);
expect(body.data.private).toBe(true);
expect(body.data.permission).toBe(null);
expect(body.data.name).toBe(collection.name);
});
@@ -1190,14 +1190,14 @@ describe("#collections.update", () => {
body: {
token: user.getJwtToken(),
id: collection.id,
private: true,
permission: null,
name: "Test",
},
});
const body = await res.json();
expect(res.status).toEqual(200);
expect(body.data.name).toBe("Test");
expect(body.data.private).toBe(true);
expect(body.data.permission).toBe(null);
// ensure we return with a write level policy
expect(body.policies.length).toBe(1);
@@ -1206,7 +1206,7 @@ describe("#collections.update", () => {
it("allows editing from private to non-private collection", async () => {
const { user, collection } = await seed();
collection.private = true;
collection.permission = null;
await collection.save();
await CollectionUser.create({
@@ -1220,14 +1220,14 @@ describe("#collections.update", () => {
body: {
token: user.getJwtToken(),
id: collection.id,
private: false,
permission: "read_write",
name: "Test",
},
});
const body = await res.json();
expect(res.status).toEqual(200);
expect(body.data.name).toBe("Test");
expect(body.data.private).toBe(false);
expect(body.data.permission).toBe("read_write");
// ensure we return with a write level policy
expect(body.policies.length).toBe(1);
@@ -1236,7 +1236,7 @@ describe("#collections.update", () => {
it("allows editing by read-write collection user", async () => {
const { user, collection } = await seed();
collection.private = true;
collection.permission = null;
await collection.save();
await CollectionUser.create({
@@ -1258,7 +1258,7 @@ describe("#collections.update", () => {
it("allows editing by read-write collection group user", async () => {
const user = await buildUser();
const collection = await buildCollection({
private: true,
permission: null,
teamId: user.teamId,
});
@@ -1280,7 +1280,7 @@ describe("#collections.update", () => {
it("does not allow editing by read-only collection user", async () => {
const { user, collection } = await seed();
collection.private = true;
collection.permission = null;
await collection.save();
await CollectionUser.create({
@@ -1393,7 +1393,7 @@ describe("#collections.delete", () => {
it("allows deleting by read-write collection group user", async () => {
const user = await buildUser();
const collection = await buildCollection({
private: true,
permission: null,
teamId: user.teamId,
});
await buildCollection({

View File

@@ -50,7 +50,7 @@ describe("#documents.info", () => {
it("should not return published document in collection not a member of", async () => {
const user = await buildUser();
const collection = await buildCollection({
private: true,
permission: null,
teamId: user.teamId,
});
const document = await buildDocument({ collectionId: collection.id });
@@ -209,7 +209,7 @@ describe("#documents.info", () => {
userId: user.id,
});
collection.private = true;
collection.permission = null;
await collection.save();
const res = await server.post("/api/documents.info", {
@@ -282,7 +282,7 @@ describe("#documents.export", () => {
it("should not return published document in collection not a member of", async () => {
const user = await buildUser();
const collection = await buildCollection({
private: true,
permission: null,
teamId: user.teamId,
});
const document = await buildDocument({ collectionId: collection.id });
@@ -400,7 +400,7 @@ describe("#documents.export", () => {
userId: user.id,
});
collection.private = true;
collection.permission = null;
await collection.save();
const res = await server.post("/api/documents.export", {
@@ -501,7 +501,7 @@ describe("#documents.list", () => {
it("should not return documents in private collections not a member of", async () => {
const { user, collection } = await seed();
collection.private = true;
collection.permission = null;
await collection.save();
const res = await server.post("/api/documents.list", {
@@ -573,7 +573,7 @@ describe("#documents.list", () => {
it("should allow filtering to private collection", async () => {
const { user, collection } = await seed();
collection.private = true;
collection.permission = null;
await collection.save();
await CollectionUser.create({
@@ -647,7 +647,7 @@ describe("#documents.pinned", () => {
it("should return pinned documents in private collections member of", async () => {
const { user, collection, document } = await seed();
collection.private = true;
collection.permission = null;
await collection.save();
document.pinnedById = user.id;
@@ -672,7 +672,7 @@ describe("#documents.pinned", () => {
it("should not return pinned documents in private collections not a member of", async () => {
const collection = await buildCollection({
private: true,
permission: null,
});
const user = await buildUser({ teamId: collection.teamId });
@@ -710,7 +710,7 @@ describe("#documents.drafts", () => {
document.publishedAt = null;
await document.save();
collection.private = true;
collection.permission = null;
await collection.save();
const res = await server.post("/api/documents.drafts", {
@@ -996,7 +996,7 @@ describe("#documents.search", () => {
it("should return documents for a specific private collection", async () => {
const { user, collection } = await seed();
collection.private = true;
collection.permission = null;
await collection.save();
await CollectionUser.create({
@@ -1061,7 +1061,7 @@ describe("#documents.search", () => {
it("should not return documents in private collections not a member of", async () => {
const { user } = await seed();
const collection = await buildCollection({ private: true });
const collection = await buildCollection({ permission: null });
await buildDocument({
title: "search term",
@@ -1158,7 +1158,7 @@ describe("#documents.archived", () => {
it("should not return documents in private collections not a member of", async () => {
const { user } = await seed();
const collection = await buildCollection({ private: true });
const collection = await buildCollection({ permission: null });
const document = await buildDocument({
teamId: user.teamId,
@@ -1224,7 +1224,7 @@ describe("#documents.viewed", () => {
it("should not return recently viewed documents in collection not a member of", async () => {
const { user, document, collection } = await seed();
await View.increment({ documentId: document.id, userId: user.id });
collection.private = true;
collection.permission = null;
await collection.save();
const res = await server.post("/api/documents.viewed", {
@@ -1808,7 +1808,7 @@ describe("#documents.update", () => {
document.publishedAt = null;
await document.save();
collection.private = true;
collection.permission = null;
await collection.save();
await CollectionUser.create({
@@ -1903,7 +1903,7 @@ describe("#documents.update", () => {
it("allows editing by read-write collection user", async () => {
const { admin, document, collection } = await seed();
collection.private = true;
collection.permission = null;
await collection.save();
await CollectionUser.create({
@@ -1931,7 +1931,7 @@ describe("#documents.update", () => {
it("does not allow editing by read-only collection user", async () => {
const { user, document, collection } = await seed();
collection.private = true;
collection.permission = null;
await collection.save();
await CollectionUser.create({
@@ -1953,6 +1953,23 @@ describe("#documents.update", () => {
expect(res.status).toEqual(403);
});
it("does not allow editing in read-only collection", async () => {
const { user, document, collection } = await seed();
collection.permission = "read";
await collection.save();
const res = await server.post("/api/documents.update", {
body: {
token: user.getJwtToken(),
id: document.id,
text: "Changed text",
lastRevision: document.revision,
},
});
expect(res.status).toEqual(403);
});
it("should append document with text", async () => {
const { user, document } = await seed();

View File

@@ -66,7 +66,7 @@ describe("#revisions.list", () => {
const { user, document, collection } = await seed();
await Revision.createFromDocument(document);
collection.private = true;
collection.permission = null;
await collection.save();
const res = await server.post("/api/revisions.list", {

View File

@@ -115,7 +115,7 @@ describe("#shares.list", () => {
userId: admin.id,
});
collection.private = true;
collection.permission = null;
await collection.save();
const res = await server.post("/api/shares.list", {
@@ -151,7 +151,7 @@ describe("#shares.create", () => {
it("should not allow creating a share record with read-only permissions", async () => {
const { user, document, collection } = await seed();
collection.private = true;
collection.permission = null;
await collection.save();

View File

@@ -27,7 +27,7 @@ describe("#views.list", () => {
it("should return views for a document in read-only collection", async () => {
const { user, document, collection } = await seed();
collection.private = true;
collection.permission = null;
await collection.save();
await CollectionUser.create({
@@ -84,7 +84,7 @@ describe("#views.create", () => {
it("should allow creating a view record for document in read-only collection", async () => {
const { user, document, collection } = await seed();
collection.private = true;
collection.permission = null;
await collection.save();
await CollectionUser.create({