Skip to content

Commit 8c68898

Browse files
committed
Merge remote-tracking branch 'refs/remotes/origin/dev' into next
# Conflicts: # packages/pulumi-aws/package.json # packages/pulumi-sdk/package.json # packages/pulumi/package.json # packages/serverless-cms-aws/package.json # yarn.lock
2 parents 20bf08f + f677787 commit 8c68898

File tree

114 files changed

+1940
-938
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

114 files changed

+1940
-938
lines changed

.adiorc.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ module.exports = {
3636
"https",
3737
"inspector",
3838
"node:fs",
39+
"node:timers",
40+
"node:path",
3941
"os",
4042
"path",
4143
"readline",

packages/api-aco/__tests__/flp.apiTokens.test.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const identityB: SecurityIdentity = { id: "2", type: "admin", displayName: "B" }
66
const identityApiToken: SecurityIdentity = { id: "3", type: "api-token", displayName: "API Token" };
77

88
describe("Folder Level Permissions - API Tokens", () => {
9-
it("as a user with 'viewer' access to a folder, I should not be able to create, update, or delete content in it", async () => {
9+
it("FLPs should not interfere with API tokens", async () => {
1010
const gqlIdentityA = useGraphQlHandler({ identity: identityA });
1111

1212
const gqlIdentityApiToken = useGraphQlHandler({
@@ -44,6 +44,7 @@ describe("Folder Level Permissions - API Tokens", () => {
4444

4545
// Set identity B as viewer of the folder. We need this just so FLP kicks in.
4646
// Otherwise, anybody can access content in the folder, no FLPs are applied.
47+
// In any case, this should not affect the API key.
4748
await gqlIdentityA.aco.updateFolder({
4849
id: folder.id,
4950
data: {
@@ -68,7 +69,7 @@ describe("Folder Level Permissions - API Tokens", () => {
6869
error: null
6970
});
7071

71-
// Listing content in the folder should be now allowed for identity C.
72+
// Listing content in the folder should be allowed for API key.
7273
await expect(
7374
gqlIdentityApiToken.cms
7475
.listEntries(model, {
@@ -91,7 +92,7 @@ describe("Folder Level Permissions - API Tokens", () => {
9192
}
9293
});
9394

94-
// Creating content in the folder should be forbidden for identity C.
95+
// Creating content in the folder should be allowed with an API key.
9596
await expect(
9697
gqlIdentityApiToken.cms
9798
.createEntry(model, {
@@ -109,7 +110,7 @@ describe("Folder Level Permissions - API Tokens", () => {
109110
data: { id: expect.any(String) }
110111
});
111112

112-
// Updating content in the folder should be forbidden for identity C.
113+
// Updating content in the folder should be allowed with an API key.
113114
await expect(
114115
gqlIdentityApiToken.cms
115116
.updateEntry(model, {
@@ -123,7 +124,7 @@ describe("Folder Level Permissions - API Tokens", () => {
123124
data: { title: createdEntry.title + "-update" }
124125
});
125126

126-
// Deleting a file in the folder should be now allowed for identity C.
127+
// Deleting content in the folder should be allowed with an API key.
127128
await expect(
128129
gqlIdentityApiToken.cms
129130
.deleteEntry(model, { revision: createdEntry.entryId })

packages/api-aco/__tests__/flp.cms.test.ts

Lines changed: 91 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,11 @@
11
import { useGraphQlHandler } from "./utils/useGraphQlHandler";
22
import { SecurityIdentity } from "@webiny/api-security/types";
3+
import { expectNotAuthorized } from "./utils/expectNotAuthorized";
34

45
const identityA: SecurityIdentity = { id: "1", type: "admin", displayName: "A" };
56
const identityB: SecurityIdentity = { id: "2", type: "admin", displayName: "B" };
67
const identityC: SecurityIdentity = { id: "3", type: "admin", displayName: "C" };
78

8-
const expectNotAuthorized = async (promise: Promise<any>) => {
9-
await expect(promise).resolves.toEqual({
10-
data: null,
11-
error: {
12-
code: "SECURITY_NOT_AUTHORIZED",
13-
data: null,
14-
message: "Not authorized!"
15-
}
16-
});
17-
};
18-
199
describe("Folder Level Permissions - File Manager GraphQL API", () => {
2010
const gqlIdentityA = useGraphQlHandler({ identity: identityA });
2111

@@ -487,4 +477,94 @@ describe("Folder Level Permissions - File Manager GraphQL API", () => {
487477
})
488478
).resolves.toMatchObject({ data: true, error: null });
489479
});
480+
481+
test("as a user, I should not be able to delete folders that have content they cannot see", async () => {
482+
const gqlIdentityA = useGraphQlHandler({ identity: identityA });
483+
const gqlIdentityC = useGraphQlHandler({
484+
identity: identityC,
485+
permissions: [{ name: "cms.*" }]
486+
});
487+
488+
const modelGroup = await gqlIdentityA.cms.createTestModelGroup();
489+
const model = await gqlIdentityA.cms.createBasicModel({ modelGroup: modelGroup.id });
490+
491+
const folderA = await gqlIdentityA.aco
492+
.createFolder({
493+
data: {
494+
title: "Folder A",
495+
slug: "folder-a",
496+
type: `cms:${model.modelId}`
497+
}
498+
})
499+
.then(([response]) => {
500+
return response.data.aco.createFolder.data;
501+
});
502+
503+
const folderB = await gqlIdentityA.aco
504+
.createFolder({
505+
data: {
506+
title: "Folder B",
507+
slug: "folder-b",
508+
parentId: folderA.id,
509+
type: `cms:${model.modelId}`
510+
}
511+
})
512+
.then(([response]) => {
513+
return response.data.aco.createFolder.data;
514+
});
515+
516+
for (let i = 1; i <= 4; i++) {
517+
await gqlIdentityA.cms.createEntry(model, { data: { title: `Test-${i}` } });
518+
}
519+
520+
// Deleting folderA should be forbidden because there is content in it. In this case,
521+
// user actually sees this content, so we expect a "delete all child folders and files"
522+
// error, not a "not authorized" error.
523+
await expect(
524+
gqlIdentityC.aco.deleteFolder({ id: folderA.id }).then(([response]) => {
525+
return response.data.aco.deleteFolder;
526+
})
527+
).resolves.toMatchObject({
528+
data: null,
529+
error: {
530+
code: "DELETE_FOLDER_WITH_CHILDREN",
531+
data: {
532+
folder: {
533+
slug: "folder-a"
534+
},
535+
hasFolders: true,
536+
hasContent: false
537+
},
538+
message: "Delete all child folders and entries before proceeding."
539+
}
540+
});
541+
542+
// Only identity B (and identity A, the owner) can see the folder B and its files.
543+
await gqlIdentityA.aco.updateFolder({
544+
id: folderB.id,
545+
data: {
546+
permissions: [
547+
{
548+
target: `admin:${identityB.id}`,
549+
level: "owner"
550+
}
551+
]
552+
}
553+
});
554+
555+
// Again, deleting folderA should be forbidden because there is content in it. In this
556+
// case, user doesn't see this content, so we expect a "not authorized" error.
557+
await expectNotAuthorized(
558+
gqlIdentityC.aco.deleteFolder({ id: folderA.id }).then(([response]) => {
559+
return response.data.aco.deleteFolder;
560+
}),
561+
{
562+
folder: { id: folderA.id },
563+
564+
// There are no entries in the folder, but there is one invisible / inaccessible folder.
565+
hasContent: false,
566+
hasFolders: true
567+
}
568+
);
569+
});
490570
});

packages/api-aco/__tests__/flp.fm.test.ts

Lines changed: 86 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useGraphQlHandler } from "./utils/useGraphQlHandler";
2+
import { expectNotAuthorized } from "./utils/expectNotAuthorized";
23
import { SecurityIdentity } from "@webiny/api-security/types";
34
import { mdbid } from "@webiny/utils";
45

@@ -10,17 +11,6 @@ const identityA: SecurityIdentity = { id: "1", type: "admin", displayName: "A" }
1011
const identityB: SecurityIdentity = { id: "2", type: "admin", displayName: "B" };
1112
const identityC: SecurityIdentity = { id: "3", type: "admin", displayName: "C" };
1213

13-
const expectNotAuthorized = async (promise: Promise<any>, data: Record<string, any> = null) => {
14-
await expect(promise).resolves.toEqual({
15-
data: null,
16-
error: {
17-
code: "SECURITY_NOT_AUTHORIZED",
18-
data,
19-
message: "Not authorized!"
20-
}
21-
});
22-
};
23-
2414
const createSampleFileData = (overrides: Record<string, any> = {}) => {
2515
const id = mdbid();
2616
return {
@@ -298,4 +288,89 @@ describe("Folder Level Permissions - File Manager GraphQL API", () => {
298288
).resolves.toMatchObject({ data: true, error: null });
299289
}
300290
});
291+
292+
test("as a user, I should not be able to delete folders that have content they cannot see", async () => {
293+
const folderA = await gqlIdentityA.aco
294+
.createFolder({
295+
data: {
296+
title: "Folder A",
297+
slug: "folder-a",
298+
type: FOLDER_TYPE
299+
}
300+
})
301+
.then(([response]) => {
302+
return response.data.aco.createFolder.data;
303+
});
304+
305+
const folderB = await gqlIdentityA.aco
306+
.createFolder({
307+
data: {
308+
title: "Folder B",
309+
slug: "folder-b",
310+
parentId: folderA.id,
311+
type: FOLDER_TYPE
312+
}
313+
})
314+
.then(([response]) => {
315+
return response.data.aco.createFolder.data;
316+
});
317+
318+
for (let i = 1; i <= 4; i++) {
319+
await gqlIdentityA.fm.createFile({
320+
data: createSampleFileData({
321+
location: { folderId: folderB.id }
322+
})
323+
});
324+
}
325+
326+
// Deleting folderA should be forbidden because there is content in it. In this case,
327+
// user actually sees this content, so we expect a "delete all child folders and files"
328+
// error, not a "not authorized" error.
329+
await expect(
330+
gqlIdentityC.aco.deleteFolder({ id: folderA.id }).then(([response]) => {
331+
return response.data.aco.deleteFolder;
332+
})
333+
).resolves.toMatchObject({
334+
data: null,
335+
error: {
336+
code: "DELETE_FOLDER_WITH_CHILDREN",
337+
data: {
338+
folder: {
339+
slug: "folder-a"
340+
},
341+
hasFolders: true,
342+
hasContent: false
343+
},
344+
message: "Delete all child folders and entries before proceeding."
345+
}
346+
});
347+
348+
// Only identity B (and identity A, the owner) can see the folder B and its files.
349+
await gqlIdentityA.aco.updateFolder({
350+
id: folderB.id,
351+
data: {
352+
permissions: [
353+
{
354+
target: `admin:${identityB.id}`,
355+
level: "owner"
356+
}
357+
]
358+
}
359+
});
360+
361+
// Again, deleting folderA should be forbidden because there is content in it. In this
362+
// case, user doesn't see this content, so we expect a "not authorized" error.
363+
await expectNotAuthorized(
364+
gqlIdentityC.aco.deleteFolder({ id: folderA.id }).then(([response]) => {
365+
return response.data.aco.deleteFolder;
366+
}),
367+
{
368+
folder: { id: folderA.id },
369+
370+
// There are no entries in the folder, but there is one invisible / inaccessible folder.
371+
hasContent: false,
372+
hasFolders: true
373+
}
374+
);
375+
});
301376
});

packages/api-aco/__tests__/folder.flp.crud.test.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useGraphQlHandler } from "./utils/useGraphQlHandler";
22
import { SecurityIdentity } from "@webiny/api-security/types";
3+
import { expectNotAuthorized } from "./utils/expectNotAuthorized";
34

45
const FOLDER_TYPE = "test-folders";
56

@@ -625,4 +626,88 @@ describe("Folder Level Permissions", () => {
625626
}
626627
});
627628
});
629+
630+
test("as a user, I should not be able to delete folders that have content they cannot see", async () => {
631+
const identityA: SecurityIdentity = { id: "1", type: "admin", displayName: "A" };
632+
const identityB: SecurityIdentity = { id: "2", type: "admin", displayName: "B" };
633+
const identityC: SecurityIdentity = { id: "3", type: "admin", displayName: "C" };
634+
635+
const gqlIdentityA = useGraphQlHandler({ identity: identityA });
636+
const gqlIdentityC = useGraphQlHandler({ identity: identityC, permissions: [] });
637+
638+
const folderA = await gqlIdentityA.aco
639+
.createFolder({
640+
data: {
641+
title: "Folder A",
642+
slug: "folder-a",
643+
type: FOLDER_TYPE
644+
}
645+
})
646+
.then(([response]) => {
647+
return response.data.aco.createFolder.data;
648+
});
649+
650+
const folderB = await gqlIdentityA.aco
651+
.createFolder({
652+
data: {
653+
title: "Folder B",
654+
slug: "folder-b",
655+
parentId: folderA.id,
656+
type: FOLDER_TYPE
657+
}
658+
})
659+
.then(([response]) => {
660+
return response.data.aco.createFolder.data;
661+
});
662+
663+
// Deleting folderA should be forbidden because there is content in it. In this case,
664+
// user actually sees this content, so we expect a "delete all child folders and files"
665+
// error, not a "not authorized" error.
666+
await expect(
667+
gqlIdentityC.aco.deleteFolder({ id: folderA.id }).then(([response]) => {
668+
return response.data.aco.deleteFolder;
669+
})
670+
).resolves.toMatchObject({
671+
data: null,
672+
error: {
673+
code: "DELETE_FOLDER_WITH_CHILDREN",
674+
data: {
675+
folder: {
676+
slug: "folder-a"
677+
},
678+
hasFolders: true,
679+
hasContent: false
680+
},
681+
message: "Delete all child folders and entries before proceeding."
682+
}
683+
});
684+
685+
// Only identity B (and identity A, the owner) can see the folder B and its files.
686+
await gqlIdentityA.aco.updateFolder({
687+
id: folderB.id,
688+
data: {
689+
permissions: [
690+
{
691+
target: `admin:${identityB.id}`,
692+
level: "owner"
693+
}
694+
]
695+
}
696+
});
697+
698+
// Again, deleting folderA should be forbidden because there is content in it. In this
699+
// case, user doesn't see this content, so we expect a "not authorized" error.
700+
await expectNotAuthorized(
701+
gqlIdentityC.aco.deleteFolder({ id: folderA.id }).then(([response]) => {
702+
return response.data.aco.deleteFolder;
703+
}),
704+
{
705+
folder: { id: folderA.id },
706+
707+
// There are no entries in the folder, but there is one invisible / inaccessible folder.
708+
hasContent: false,
709+
hasFolders: true
710+
}
711+
);
712+
});
628713
});

packages/api-aco/__tests__/folder.so.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ describe("`folder` CRUD", () => {
317317
data: null,
318318
error: expect.objectContaining({
319319
code: "DELETE_FOLDER_WITH_CHILDREN",
320-
message: "Delete all child folders and files before proceeding."
320+
message: "Delete all child folders and entries before proceeding."
321321
})
322322
}
323323
}

0 commit comments

Comments
 (0)