Skip to content

Commit

Permalink
Merge remote-tracking branch 'refs/remotes/origin/dev' into next
Browse files Browse the repository at this point in the history
# Conflicts:
#	packages/pulumi-aws/package.json
#	packages/pulumi-sdk/package.json
#	packages/pulumi/package.json
#	packages/serverless-cms-aws/package.json
#	yarn.lock
  • Loading branch information
adrians5j committed Aug 19, 2024
2 parents 20bf08f + f677787 commit 8c68898
Show file tree
Hide file tree
Showing 114 changed files with 1,940 additions and 938 deletions.
2 changes: 2 additions & 0 deletions .adiorc.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ module.exports = {
"https",
"inspector",
"node:fs",
"node:timers",
"node:path",
"os",
"path",
"readline",
Expand Down
11 changes: 6 additions & 5 deletions packages/api-aco/__tests__/flp.apiTokens.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const identityB: SecurityIdentity = { id: "2", type: "admin", displayName: "B" }
const identityApiToken: SecurityIdentity = { id: "3", type: "api-token", displayName: "API Token" };

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

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

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

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

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

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

// Deleting a file in the folder should be now allowed for identity C.
// Deleting content in the folder should be allowed with an API key.
await expect(
gqlIdentityApiToken.cms
.deleteEntry(model, { revision: createdEntry.entryId })
Expand Down
102 changes: 91 additions & 11 deletions packages/api-aco/__tests__/flp.cms.test.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,11 @@
import { useGraphQlHandler } from "./utils/useGraphQlHandler";
import { SecurityIdentity } from "@webiny/api-security/types";
import { expectNotAuthorized } from "./utils/expectNotAuthorized";

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

const expectNotAuthorized = async (promise: Promise<any>) => {
await expect(promise).resolves.toEqual({
data: null,
error: {
code: "SECURITY_NOT_AUTHORIZED",
data: null,
message: "Not authorized!"
}
});
};

describe("Folder Level Permissions - File Manager GraphQL API", () => {
const gqlIdentityA = useGraphQlHandler({ identity: identityA });

Expand Down Expand Up @@ -487,4 +477,94 @@ describe("Folder Level Permissions - File Manager GraphQL API", () => {
})
).resolves.toMatchObject({ data: true, error: null });
});

test("as a user, I should not be able to delete folders that have content they cannot see", async () => {
const gqlIdentityA = useGraphQlHandler({ identity: identityA });
const gqlIdentityC = useGraphQlHandler({
identity: identityC,
permissions: [{ name: "cms.*" }]
});

const modelGroup = await gqlIdentityA.cms.createTestModelGroup();
const model = await gqlIdentityA.cms.createBasicModel({ modelGroup: modelGroup.id });

const folderA = await gqlIdentityA.aco
.createFolder({
data: {
title: "Folder A",
slug: "folder-a",
type: `cms:${model.modelId}`
}
})
.then(([response]) => {
return response.data.aco.createFolder.data;
});

const folderB = await gqlIdentityA.aco
.createFolder({
data: {
title: "Folder B",
slug: "folder-b",
parentId: folderA.id,
type: `cms:${model.modelId}`
}
})
.then(([response]) => {
return response.data.aco.createFolder.data;
});

for (let i = 1; i <= 4; i++) {
await gqlIdentityA.cms.createEntry(model, { data: { title: `Test-${i}` } });
}

// Deleting folderA should be forbidden because there is content in it. In this case,
// user actually sees this content, so we expect a "delete all child folders and files"
// error, not a "not authorized" error.
await expect(
gqlIdentityC.aco.deleteFolder({ id: folderA.id }).then(([response]) => {
return response.data.aco.deleteFolder;
})
).resolves.toMatchObject({
data: null,
error: {
code: "DELETE_FOLDER_WITH_CHILDREN",
data: {
folder: {
slug: "folder-a"
},
hasFolders: true,
hasContent: false
},
message: "Delete all child folders and entries before proceeding."
}
});

// Only identity B (and identity A, the owner) can see the folder B and its files.
await gqlIdentityA.aco.updateFolder({
id: folderB.id,
data: {
permissions: [
{
target: `admin:${identityB.id}`,
level: "owner"
}
]
}
});

// Again, deleting folderA should be forbidden because there is content in it. In this
// case, user doesn't see this content, so we expect a "not authorized" error.
await expectNotAuthorized(
gqlIdentityC.aco.deleteFolder({ id: folderA.id }).then(([response]) => {
return response.data.aco.deleteFolder;
}),
{
folder: { id: folderA.id },

// There are no entries in the folder, but there is one invisible / inaccessible folder.
hasContent: false,
hasFolders: true
}
);
});
});
97 changes: 86 additions & 11 deletions packages/api-aco/__tests__/flp.fm.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useGraphQlHandler } from "./utils/useGraphQlHandler";
import { expectNotAuthorized } from "./utils/expectNotAuthorized";
import { SecurityIdentity } from "@webiny/api-security/types";
import { mdbid } from "@webiny/utils";

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

const expectNotAuthorized = async (promise: Promise<any>, data: Record<string, any> = null) => {
await expect(promise).resolves.toEqual({
data: null,
error: {
code: "SECURITY_NOT_AUTHORIZED",
data,
message: "Not authorized!"
}
});
};

const createSampleFileData = (overrides: Record<string, any> = {}) => {
const id = mdbid();
return {
Expand Down Expand Up @@ -298,4 +288,89 @@ describe("Folder Level Permissions - File Manager GraphQL API", () => {
).resolves.toMatchObject({ data: true, error: null });
}
});

test("as a user, I should not be able to delete folders that have content they cannot see", async () => {
const folderA = await gqlIdentityA.aco
.createFolder({
data: {
title: "Folder A",
slug: "folder-a",
type: FOLDER_TYPE
}
})
.then(([response]) => {
return response.data.aco.createFolder.data;
});

const folderB = await gqlIdentityA.aco
.createFolder({
data: {
title: "Folder B",
slug: "folder-b",
parentId: folderA.id,
type: FOLDER_TYPE
}
})
.then(([response]) => {
return response.data.aco.createFolder.data;
});

for (let i = 1; i <= 4; i++) {
await gqlIdentityA.fm.createFile({
data: createSampleFileData({
location: { folderId: folderB.id }
})
});
}

// Deleting folderA should be forbidden because there is content in it. In this case,
// user actually sees this content, so we expect a "delete all child folders and files"
// error, not a "not authorized" error.
await expect(
gqlIdentityC.aco.deleteFolder({ id: folderA.id }).then(([response]) => {
return response.data.aco.deleteFolder;
})
).resolves.toMatchObject({
data: null,
error: {
code: "DELETE_FOLDER_WITH_CHILDREN",
data: {
folder: {
slug: "folder-a"
},
hasFolders: true,
hasContent: false
},
message: "Delete all child folders and entries before proceeding."
}
});

// Only identity B (and identity A, the owner) can see the folder B and its files.
await gqlIdentityA.aco.updateFolder({
id: folderB.id,
data: {
permissions: [
{
target: `admin:${identityB.id}`,
level: "owner"
}
]
}
});

// Again, deleting folderA should be forbidden because there is content in it. In this
// case, user doesn't see this content, so we expect a "not authorized" error.
await expectNotAuthorized(
gqlIdentityC.aco.deleteFolder({ id: folderA.id }).then(([response]) => {
return response.data.aco.deleteFolder;
}),
{
folder: { id: folderA.id },

// There are no entries in the folder, but there is one invisible / inaccessible folder.
hasContent: false,
hasFolders: true
}
);
});
});
85 changes: 85 additions & 0 deletions packages/api-aco/__tests__/folder.flp.crud.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useGraphQlHandler } from "./utils/useGraphQlHandler";
import { SecurityIdentity } from "@webiny/api-security/types";
import { expectNotAuthorized } from "./utils/expectNotAuthorized";

const FOLDER_TYPE = "test-folders";

Expand Down Expand Up @@ -625,4 +626,88 @@ describe("Folder Level Permissions", () => {
}
});
});

test("as a user, I should not be able to delete folders that have content they cannot see", async () => {
const identityA: SecurityIdentity = { id: "1", type: "admin", displayName: "A" };
const identityB: SecurityIdentity = { id: "2", type: "admin", displayName: "B" };
const identityC: SecurityIdentity = { id: "3", type: "admin", displayName: "C" };

const gqlIdentityA = useGraphQlHandler({ identity: identityA });
const gqlIdentityC = useGraphQlHandler({ identity: identityC, permissions: [] });

const folderA = await gqlIdentityA.aco
.createFolder({
data: {
title: "Folder A",
slug: "folder-a",
type: FOLDER_TYPE
}
})
.then(([response]) => {
return response.data.aco.createFolder.data;
});

const folderB = await gqlIdentityA.aco
.createFolder({
data: {
title: "Folder B",
slug: "folder-b",
parentId: folderA.id,
type: FOLDER_TYPE
}
})
.then(([response]) => {
return response.data.aco.createFolder.data;
});

// Deleting folderA should be forbidden because there is content in it. In this case,
// user actually sees this content, so we expect a "delete all child folders and files"
// error, not a "not authorized" error.
await expect(
gqlIdentityC.aco.deleteFolder({ id: folderA.id }).then(([response]) => {
return response.data.aco.deleteFolder;
})
).resolves.toMatchObject({
data: null,
error: {
code: "DELETE_FOLDER_WITH_CHILDREN",
data: {
folder: {
slug: "folder-a"
},
hasFolders: true,
hasContent: false
},
message: "Delete all child folders and entries before proceeding."
}
});

// Only identity B (and identity A, the owner) can see the folder B and its files.
await gqlIdentityA.aco.updateFolder({
id: folderB.id,
data: {
permissions: [
{
target: `admin:${identityB.id}`,
level: "owner"
}
]
}
});

// Again, deleting folderA should be forbidden because there is content in it. In this
// case, user doesn't see this content, so we expect a "not authorized" error.
await expectNotAuthorized(
gqlIdentityC.aco.deleteFolder({ id: folderA.id }).then(([response]) => {
return response.data.aco.deleteFolder;
}),
{
folder: { id: folderA.id },

// There are no entries in the folder, but there is one invisible / inaccessible folder.
hasContent: false,
hasFolders: true
}
);
});
});
2 changes: 1 addition & 1 deletion packages/api-aco/__tests__/folder.so.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ describe("`folder` CRUD", () => {
data: null,
error: expect.objectContaining({
code: "DELETE_FOLDER_WITH_CHILDREN",
message: "Delete all child folders and files before proceeding."
message: "Delete all child folders and entries before proceeding."
})
}
}
Expand Down
Loading

0 comments on commit 8c68898

Please sign in to comment.