diff --git a/packages/common/src/discussions/api/types.ts b/packages/common/src/discussions/api/types.ts index 092da9ceb78..eb2c83cf1d6 100644 --- a/packages/common/src/discussions/api/types.ts +++ b/packages/common/src/discussions/api/types.ts @@ -91,7 +91,7 @@ export enum PostStatus { } /** - * possible discussionn content types, i.e. a post can be about an item, dataset, or group + * possible discussion content types, i.e. a post can be about an item, dataset, or group * * @export * @enum {string} diff --git a/packages/discussions/src/utils/channel-permission.ts b/packages/discussions/src/utils/channel-permission.ts index d5137842b4e..10ecdb0c280 100644 --- a/packages/discussions/src/utils/channel-permission.ts +++ b/packages/discussions/src/utils/channel-permission.ts @@ -11,6 +11,7 @@ import { } from "../types"; import { CANNOT_DISCUSS } from "./constants"; import { isOrgAdmin, userHasPrivilege } from "./platform"; +import { dtoToChannel } from "./channels/channel-to-dto-map"; type PermissionsByAclCategoryMap = { [key in AclCategory]?: IChannelAclPermission[]; @@ -26,7 +27,8 @@ enum ChannelAction { } // See confluence for privs documentation: https://confluencewikidev.esri.com/pages/viewpage.action?pageId=153747776#Roles&Privileges-ApplicationtoChannels -const CHANNEL_ACTION_PRIVS = { +const CHANNEL_ACTION_PRIVS: Record = { + // permissions UPDATE_OWNERS: [Role.OWNER], UPDATE_MANAGERS: [Role.OWNER, Role.MANAGE], UPDATE_MODERATORS: [Role.OWNER, Role.MANAGE], @@ -35,8 +37,10 @@ const CHANNEL_ACTION_PRIVS = { UPDATE_USERS: [Role.OWNER, Role.MANAGE], UPDATE_AUTHENTICATED_USERS: [Role.OWNER, Role.MANAGE], UPDATE_ANONYMOUS_USERS: [Role.OWNER, Role.MANAGE], + // settings UPDATE_POST_REPLIES: [Role.OWNER, Role.MANAGE, Role.MODERATE], UPDATE_POST_REACTIONS: [Role.OWNER, Role.MANAGE, Role.MODERATE], + UPDATE_POST_AS_ANONYMOUS: [Role.OWNER, Role.MANAGE, Role.MODERATE], UPDATE_ALLOWED_REACTIONS: [Role.OWNER, Role.MANAGE, Role.MODERATE], UPDATE_DEFAULT_POST_STATUS: [Role.OWNER, Role.MANAGE, Role.MODERATE], UPDATE_BLOCKED_WORDS: [Role.OWNER, Role.MANAGE, Role.MODERATE], @@ -51,6 +55,7 @@ const CHANNEL_ACTION_PRIVS = { export class ChannelPermission { private readonly ALLOWED_GROUP_MEMBER_TYPES = ["owner", "admin", "member"]; private isChannelAclEmpty: boolean; + private existingChannel: IChannel; private permissionsByCategory: PermissionsByAclCategoryMap; private channelCreator: string; private channelOrgId: string; @@ -61,6 +66,7 @@ export class ChannelPermission { "channel.channelAcl is required for ChannelPermission checks" ); } + this.existingChannel = channel; this.isChannelAclEmpty = channel.channelAcl.length === 0; this.permissionsByCategory = {}; this.channelCreator = channel.creator; @@ -134,40 +140,69 @@ export class ChannelPermission { ); } - /** - * Expose this function and call from the can-modify-channel.ts file when V2 released - * @internal - */ canUpdateProperties( user: IDiscussionsUser, - updates: IUpdateChannel + updateData: IUpdateChannel = {} ): boolean { + if (Object.keys(updateData).length === 0) { + return true; + } const userRole = this.determineUserRole(user); + const updates = dtoToChannel(updateData); if ( - // @TODO when we have access to channel ACL obj when v2 udpate-channel-dto is hoisted we can add these additional property action checks - // add or remove owners - // add or remove managers - // add or remove moderators - // add or remove orgs - // add or remove groups - // add or remove users - // add or remove authenticated users - // update anonymous users - (updates.hasOwnProperty("allowReply") && + // settings + (this.isChanged(updates.allowReply, this.existingChannel.allowReply) && !CHANNEL_ACTION_PRIVS.UPDATE_POST_REPLIES.includes(userRole)) || - (updates.hasOwnProperty("allowReaction") && + (this.isChanged( + updates.allowReaction, + this.existingChannel.allowReaction + ) && !CHANNEL_ACTION_PRIVS.UPDATE_POST_REACTIONS.includes(userRole)) || - (updates.hasOwnProperty("allowedReactions") && + (this.isChanged( + updates.allowAsAnonymous, + this.existingChannel.allowAsAnonymous + ) && + !CHANNEL_ACTION_PRIVS.UPDATE_POST_AS_ANONYMOUS.includes(userRole)) || + (this.isStringArrayChanged( + updates.allowedReactions, + this.existingChannel.allowedReactions + ) && !CHANNEL_ACTION_PRIVS.UPDATE_ALLOWED_REACTIONS.includes(userRole)) || - (updates.hasOwnProperty("defaultPostStatus") && + (this.isChanged( + updates.defaultPostStatus, + this.existingChannel.defaultPostStatus + ) && !CHANNEL_ACTION_PRIVS.UPDATE_DEFAULT_POST_STATUS.includes(userRole)) || - (updates.hasOwnProperty("blockWords") && + (this.isStringArrayChanged( + updates.blockWords, + this.existingChannel.blockWords + ) && !CHANNEL_ACTION_PRIVS.UPDATE_BLOCKED_WORDS.includes(userRole)) || - (updates.hasOwnProperty("name") && + (this.isChanged(updates.name, this.existingChannel.name) && !CHANNEL_ACTION_PRIVS.UPDATE_CHANNEL_NAME.includes(userRole)) || - (updates.hasOwnProperty("softDelete") && - !CHANNEL_ACTION_PRIVS.UPDATE_SOFT_DELETE_SETTING.includes(userRole)) + (this.isChanged(updates.softDelete, this.existingChannel.softDelete) && + !CHANNEL_ACTION_PRIVS.UPDATE_SOFT_DELETE_SETTING.includes(userRole)) || + // permissions + (this.isRoleChanged(Role.OWNER, updates.channelAcl) && + !CHANNEL_ACTION_PRIVS.UPDATE_OWNERS.includes(userRole)) || + (this.isRoleChanged(Role.MANAGE, updates.channelAcl) && + !CHANNEL_ACTION_PRIVS.UPDATE_MANAGERS.includes(userRole)) || + (this.isRoleChanged(Role.MODERATE, updates.channelAcl) && + !CHANNEL_ACTION_PRIVS.UPDATE_MODERATORS.includes(userRole)) || + (this.isCategoryChanged(AclCategory.ORG, updates.channelAcl) && + !CHANNEL_ACTION_PRIVS.UPDATE_ORGS.includes(userRole)) || + (this.isCategoryChanged(AclCategory.GROUP, updates.channelAcl) && + !CHANNEL_ACTION_PRIVS.UPDATE_GROUPS.includes(userRole)) || + (this.isCategoryChanged(AclCategory.USER, updates.channelAcl) && + !CHANNEL_ACTION_PRIVS.UPDATE_USERS.includes(userRole)) || + (this.isCategoryChanged( + AclCategory.AUTHENTICATED_USER, + updates.channelAcl + ) && + !CHANNEL_ACTION_PRIVS.UPDATE_AUTHENTICATED_USERS.includes(userRole)) || + (this.isCategoryChanged(AclCategory.ANONYMOUS_USER, updates.channelAcl) && + !CHANNEL_ACTION_PRIVS.UPDATE_ANONYMOUS_USERS.includes(userRole)) ) { return false; } @@ -175,6 +210,56 @@ export class ChannelPermission { return true; } + private isChanged( + updateValue: number | string | boolean | undefined, + existingValue: number | string | boolean | undefined + ): boolean { + return updateValue !== undefined && updateValue !== existingValue; + } + + private isStringArrayChanged( + _a: string[] | null | undefined, + _b: string[] | null | undefined + ) { + const a = _a ?? []; + const b = _b ?? []; + return ( + a.filter((x) => !b.includes(x)).length !== 0 || + b.filter((x) => !a.includes(x)).length !== 0 + ); + } + + private isRoleChanged(role: Role, aclUpdates?: IChannelAclPermission[]) { + if (!aclUpdates) { + return false; + } + // ex: ['org_admin_1111_owner', 'group_admin_222_owner', 'user_undefined_333_owner] + const existing = this.existingChannel.channelAcl + .filter((acl) => acl.role === role) + .map((acl) => `${acl.category}_${acl.subCategory}_${acl.key}_${role}`); + const changed = aclUpdates + .filter((acl) => acl.role === role) + .map((acl) => `${acl.category}_${acl.subCategory}_${acl.key}_${role}`); + return this.isStringArrayChanged(existing, changed); + } + + private isCategoryChanged( + category: AclCategory, + aclUpdates?: IChannelAclPermission[] + ) { + if (!aclUpdates) { + return false; + } + // ex: ['org_admin_111_OWNER', 'org_member_111_readWrite', 'group_member_2222_read'] + const existing = (this.permissionsByCategory[category] ?? []).map( + (acl) => `${category}_${acl.subCategory}_${acl.key}_${acl.role}` + ); + const changed = aclUpdates + .filter((acl) => acl.category === category) + .map((acl) => `${category}_${acl.subCategory}_${acl.key}_${acl.role}`); + return this.isStringArrayChanged(existing, changed); + } + private canAnyUser(action: ChannelAction): boolean { const anonymousUserRole = this.permissionsByCategory[AclCategory.ANONYMOUS_USER]?.[0].role; diff --git a/packages/discussions/src/utils/channels/can-edit-channel.ts b/packages/discussions/src/utils/channels/can-edit-channel.ts index 61976efa973..0c8c70c050e 100644 --- a/packages/discussions/src/utils/channels/can-edit-channel.ts +++ b/packages/discussions/src/utils/channels/can-edit-channel.ts @@ -1,5 +1,5 @@ import { IUser } from "@esri/arcgis-rest-types"; -import { IChannel, IDiscussionsUser, SharingAccess } from "../../types"; +import { IChannel, IDiscussionsUser, IUpdateChannel } from "../../types"; import { ChannelPermission } from "../channel-permission"; import { isAuthorizedToModifyChannelByLegacyPermissions } from "./is-authorized-to-modify-channel-by-legacy-permissions"; import { hasOrgAdminUpdateRights } from "../portal-privilege"; @@ -8,11 +8,13 @@ import { hasOrgAdminUpdateRights } from "../portal-privilege"; * Utility to determine if User has privileges to edit a channel * @param channel * @param user + * @param updateData - !!! only include for API V2 updates * @returns {boolean} */ export function canEditChannel( channel: IChannel, - user: IUser | IDiscussionsUser = {} + user: IUser | IDiscussionsUser = {}, + updateData?: IUpdateChannel // !!! only include for API V2 updates ): boolean { if (hasOrgAdminUpdateRights(user, channel.orgId)) { return true; @@ -20,7 +22,13 @@ export function canEditChannel( if (channel.channelAcl) { const channelPermission = new ChannelPermission(channel); - return channelPermission.canModerateChannel(user as IDiscussionsUser); + return ( + channelPermission.canModerateChannel(user as IDiscussionsUser) && + channelPermission.canUpdateProperties( + user as IDiscussionsUser, + updateData + ) + ); } return isAuthorizedToModifyChannelByLegacyPermissions(user, channel); diff --git a/packages/discussions/src/utils/channels/can-modify-channel.ts b/packages/discussions/src/utils/channels/can-modify-channel.ts index 24cfef90f32..f32850e50cc 100644 --- a/packages/discussions/src/utils/channels/can-modify-channel.ts +++ b/packages/discussions/src/utils/channels/can-modify-channel.ts @@ -14,7 +14,6 @@ import { isAuthorizedToModifyChannelByLegacyPermissions } from "./is-authorized- export function canModifyChannel( channel: IChannel, user: IUser | IDiscussionsUser = {} - // channelUpdates: IUpdateChannel, ): boolean { if (isOrgAdminInOrg(user, channel.orgId)) { return true; @@ -23,7 +22,6 @@ export function canModifyChannel( if (channel.channelAcl) { const channelPermission = new ChannelPermission(channel); return channelPermission.canModerateChannel(user as IDiscussionsUser); - // for V2: && channelPermission.canUpdateProperties(user as IDiscussionsUser, channelUpdates) } return isAuthorizedToModifyChannelByLegacyPermissions(user, channel); diff --git a/packages/discussions/src/utils/channels/channel-to-dto-map.ts b/packages/discussions/src/utils/channels/channel-to-dto-map.ts new file mode 100644 index 00000000000..62c3ef50771 --- /dev/null +++ b/packages/discussions/src/utils/channels/channel-to-dto-map.ts @@ -0,0 +1,11 @@ +import { IChannel, IUpdateChannel } from "@esri/hub-common"; + +// TODO: V2 use IUpdateChannel as param type when hoisted to hub.js from service +export function dtoToChannel(dto: any): IChannel { + const { channelAclDefinition, ...rest } = dto; + + return { + ...rest, + channelAcl: channelAclDefinition, + }; +} diff --git a/packages/discussions/test/utils/channel-permission.test.ts b/packages/discussions/test/utils/channel-permission.test.ts index 15f1bd74b20..2724cd6bdd9 100644 --- a/packages/discussions/test/utils/channel-permission.test.ts +++ b/packages/discussions/test/utils/channel-permission.test.ts @@ -6,6 +6,7 @@ import { IChannelAclPermission, IDiscussionsUser, IUpdateChannel, + PostReaction, PostStatus, Role, } from "../../src/types"; @@ -1211,50 +1212,52 @@ describe("ChannelPermission class", () => { }); describe("canUpdateProperties", () => { - describe("update allowReply required role", () => { - it("returns true if user has a role of moderate", () => { + describe("no updates", () => { + it("returns true if no updates, regardless of role", () => { const user = buildUser(); const channelAcl = [ - { - category: AclCategory.USER, - key: user.username, - role: Role.MODERATE, - }, + { category: AclCategory.USER, key: user.username, role: Role.READ }, // bad role for update ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + allowReply: true, + channelAcl, + } as IChannel; const channelPermission = new ChannelPermission(channel); - const updates: IUpdateChannel = { - allowReply: true, - }; + const updates: IUpdateChannel = {}; expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); }); - it("returns true if user has a role of manage", () => { + it("returns true if updates undefined, regardless of role", () => { const user = buildUser(); const channelAcl = [ - { category: AclCategory.USER, key: user.username, role: Role.MANAGE }, + { category: AclCategory.USER, key: user.username, role: Role.READ }, // bad role for update ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; - const channelPermission = new ChannelPermission(channel); - - const updates: IUpdateChannel = { + const channel = { allowReply: true, - }; + channelAcl, + } as IChannel; + const channelPermission = new ChannelPermission(channel); - expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); + expect(channelPermission.canUpdateProperties(user)).toBe(true); }); + }); - it("returns true if user has a role of owner", () => { + describe("allowReply", () => { + it("returns true if value not changed from existing regardless of role", () => { const user = buildUser(); const channelAcl = [ - { category: AclCategory.USER, key: user.username, role: Role.OWNER }, + { category: AclCategory.USER, key: user.username, role: Role.READ }, // bad role for update ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + allowReply: true, + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); const updates: IUpdateChannel = { @@ -1264,30 +1267,26 @@ describe("ChannelPermission class", () => { expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); }); - it("returns false if user does not have a minimum role of moderate", () => { + it("returns true if update is undefined regardless of role", () => { const user = buildUser(); const channelAcl = [ - { - category: AclCategory.USER, - key: user.username, - role: Role.READWRITE, - }, + { category: AclCategory.USER, key: user.username, role: Role.READ }, // bad role for update ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + allowReply: true, + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); const updates: IUpdateChannel = { - allowReply: true, + allowReply: undefined, }; - expect(channelPermission.canUpdateProperties(user, updates)).toBe( - false - ); + expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); }); - }); - describe("update allowReaction required role", () => { it("returns true if user has a role of moderate", () => { const user = buildUser(); const channelAcl = [ @@ -1298,11 +1297,15 @@ describe("ChannelPermission class", () => { }, ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + allowReply: false, + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); const updates: IUpdateChannel = { - allowReaction: true, + allowReply: true, }; expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); @@ -1314,11 +1317,15 @@ describe("ChannelPermission class", () => { { category: AclCategory.USER, key: user.username, role: Role.MANAGE }, ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + allowReply: false, + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); const updates: IUpdateChannel = { - allowReaction: true, + allowReply: true, }; expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); @@ -1330,11 +1337,15 @@ describe("ChannelPermission class", () => { { category: AclCategory.USER, key: user.username, role: Role.OWNER }, ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + allowReply: false, + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); const updates: IUpdateChannel = { - allowReaction: true, + allowReply: true, }; expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); @@ -1350,11 +1361,15 @@ describe("ChannelPermission class", () => { }, ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + allowReply: false, + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); const updates: IUpdateChannel = { - allowReaction: true, + allowReply: true, }; expect(channelPermission.canUpdateProperties(user, updates)).toBe( @@ -1363,159 +1378,179 @@ describe("ChannelPermission class", () => { }); }); - describe("update allowedReactions required role", () => { - it("returns true if user has a role of moderate", () => { + describe("allowReaction", () => { + it("returns true if value not changed from existing regardless of role", () => { const user = buildUser(); const channelAcl = [ - { - category: AclCategory.USER, - key: user.username, - role: Role.MODERATE, - }, + { category: AclCategory.USER, key: user.username, role: Role.READ }, // bad role for update ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + allowReaction: true, + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); const updates: IUpdateChannel = { - allowedReactions: [], + allowReaction: true, }; expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); }); - it("returns true if user has a role of manage", () => { + it("returns true if update is undefined existing regardless of role", () => { const user = buildUser(); const channelAcl = [ - { category: AclCategory.USER, key: user.username, role: Role.MANAGE }, + { category: AclCategory.USER, key: user.username, role: Role.READ }, // bad role for update ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + allowReaction: true, + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); const updates: IUpdateChannel = { - allowedReactions: [], + allowReaction: undefined, }; expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); }); - it("returns true if user has a role of owner", () => { + it("returns true if user has a role of moderate", () => { const user = buildUser(); const channelAcl = [ - { category: AclCategory.USER, key: user.username, role: Role.OWNER }, + { + category: AclCategory.USER, + key: user.username, + role: Role.MODERATE, + }, ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + allowReaction: false, + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); const updates: IUpdateChannel = { - allowedReactions: [], + allowReaction: true, }; expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); }); - it("returns false if user does not have a minimum role of moderate", () => { + it("returns true if user has a role of manage", () => { const user = buildUser(); const channelAcl = [ - { - category: AclCategory.USER, - key: user.username, - role: Role.READWRITE, - }, + { category: AclCategory.USER, key: user.username, role: Role.MANAGE }, ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + allowReaction: false, + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); const updates: IUpdateChannel = { - allowedReactions: [], + allowReaction: true, }; - expect(channelPermission.canUpdateProperties(user, updates)).toBe( - false - ); + expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); }); - }); - describe("update defaultPostStatus required role", () => { - it("returns true if user has a role of moderate", () => { + it("returns true if user has a role of owner", () => { const user = buildUser(); const channelAcl = [ - { - category: AclCategory.USER, - key: user.username, - role: Role.MODERATE, - }, + { category: AclCategory.USER, key: user.username, role: Role.OWNER }, ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + allowReaction: false, + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); const updates: IUpdateChannel = { - defaultPostStatus: PostStatus.APPROVED, + allowReaction: true, }; expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); }); - it("returns true if user has a role of manage", () => { + it("returns false if user does not have a minimum role of moderate", () => { const user = buildUser(); const channelAcl = [ - { category: AclCategory.USER, key: user.username, role: Role.MANAGE }, + { + category: AclCategory.USER, + key: user.username, + role: Role.READWRITE, + }, ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + allowReaction: false, + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); const updates: IUpdateChannel = { - defaultPostStatus: PostStatus.APPROVED, + allowReaction: true, }; - expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + false + ); }); + }); - it("returns true if user has a role of owner", () => { + describe("allowAsAnonymous", () => { + it("returns true if value not changed from existing regardless of role", () => { const user = buildUser(); const channelAcl = [ - { category: AclCategory.USER, key: user.username, role: Role.OWNER }, + { category: AclCategory.USER, key: user.username, role: Role.READ }, // bad role for update ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + allowAsAnonymous: true, + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); const updates: IUpdateChannel = { - defaultPostStatus: PostStatus.APPROVED, + allowAsAnonymous: true, }; expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); }); - it("returns false if user does not have a minimum role of moderate", () => { + it("returns true if update is undefined regardless of role", () => { const user = buildUser(); const channelAcl = [ - { - category: AclCategory.USER, - key: user.username, - role: Role.READWRITE, - }, + { category: AclCategory.USER, key: user.username, role: Role.READ }, // bad role for update ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + allowAsAnonymous: true, + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); const updates: IUpdateChannel = { - defaultPostStatus: PostStatus.APPROVED, + allowAsAnonymous: undefined, }; - expect(channelPermission.canUpdateProperties(user, updates)).toBe( - false - ); + expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); }); - }); - describe("update blockWords required role", () => { it("returns true if user has a role of moderate", () => { const user = buildUser(); const channelAcl = [ @@ -1526,11 +1561,15 @@ describe("ChannelPermission class", () => { }, ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + allowAsAnonymous: false, + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); const updates: IUpdateChannel = { - blockWords: [], + allowAsAnonymous: true, }; expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); @@ -1542,11 +1581,15 @@ describe("ChannelPermission class", () => { { category: AclCategory.USER, key: user.username, role: Role.MANAGE }, ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + allowAsAnonymous: false, + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); const updates: IUpdateChannel = { - blockWords: [], + allowAsAnonymous: true, }; expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); @@ -1558,11 +1601,15 @@ describe("ChannelPermission class", () => { { category: AclCategory.USER, key: user.username, role: Role.OWNER }, ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + allowAsAnonymous: false, + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); const updates: IUpdateChannel = { - blockWords: [], + allowAsAnonymous: true, }; expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); @@ -1578,11 +1625,15 @@ describe("ChannelPermission class", () => { }, ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + allowAsAnonymous: false, + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); const updates: IUpdateChannel = { - blockWords: [], + allowAsAnonymous: true, }; expect(channelPermission.canUpdateProperties(user, updates)).toBe( @@ -1591,74 +1642,82 @@ describe("ChannelPermission class", () => { }); }); - describe("update softDelete required role", () => { - it("returns true if user has a role of manage", () => { + describe("allowedReactions", () => { + it("returns true if value not changed from existing regardless of role", () => { const user = buildUser(); const channelAcl = [ - { category: AclCategory.USER, key: user.username, role: Role.MANAGE }, + { category: AclCategory.USER, key: user.username, role: Role.READ }, // bad role for update ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + allowedReactions: [PostReaction.THUMBS_UP], + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); const updates: IUpdateChannel = { - softDelete: true, + allowedReactions: [PostReaction.THUMBS_UP], }; expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); }); - it("returns true if user has a role of owner", () => { + it("returns true if value not changed (just rearranged) from existing regardless of role", () => { const user = buildUser(); const channelAcl = [ - { category: AclCategory.USER, key: user.username, role: Role.OWNER }, + { category: AclCategory.USER, key: user.username, role: Role.READ }, // bad role for update ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + allowedReactions: [PostReaction.THUMBS_UP, PostReaction.LAUGH], + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); const updates: IUpdateChannel = { - softDelete: true, + allowedReactions: [PostReaction.LAUGH, PostReaction.THUMBS_UP], }; expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); }); - it("returns false if user does not have a minimum role of manage", () => { + it("returns true if user has a role of manage and reaction is removed", () => { const user = buildUser(); const channelAcl = [ - { - category: AclCategory.USER, - key: user.username, - role: Role.MODERATE, - }, + { category: AclCategory.USER, key: user.username, role: Role.MANAGE }, ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + allowedReactions: [PostReaction.THUMBS_UP, PostReaction.LAUGH], + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); const updates: IUpdateChannel = { - softDelete: true, + allowedReactions: [PostReaction.THUMBS_UP], }; - expect(channelPermission.canUpdateProperties(user, updates)).toBe( - false - ); + expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); }); - }); - describe("update name required role", () => { - it("returns true if user has a role of manage", () => { + it("returns true if user has a role of manage and reaction is added", () => { const user = buildUser(); const channelAcl = [ { category: AclCategory.USER, key: user.username, role: Role.MANAGE }, ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + allowedReactions: [PostReaction.THUMBS_UP], + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); const updates: IUpdateChannel = { - name: "foo", + allowedReactions: [PostReaction.THUMBS_UP, PostReaction.LAUGH], }; expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); @@ -1670,31 +1729,39 @@ describe("ChannelPermission class", () => { { category: AclCategory.USER, key: user.username, role: Role.OWNER }, ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + allowedReactions: [PostReaction.THUMBS_UP], + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); const updates: IUpdateChannel = { - name: "foo", + allowedReactions: [PostReaction.LAUGH], }; expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); }); - it("returns false if user does not have a minimum role of manage", () => { + it("returns false if user does not have a minimum role of moderate", () => { const user = buildUser(); const channelAcl = [ { category: AclCategory.USER, key: user.username, - role: Role.MODERATE, + role: Role.READWRITE, }, ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + allowedReactions: [PostReaction.THUMBS_UP], + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); const updates: IUpdateChannel = { - name: "foo", + allowedReactions: [PostReaction.LAUGH], }; expect(channelPermission.canUpdateProperties(user, updates)).toBe( @@ -1702,440 +1769,2508 @@ describe("ChannelPermission class", () => { ); }); }); - }); - - describe("canPostToChannel", () => { - describe("all permission cases", () => { - it("returns false if user logged in and channel permissions are empty", async () => { - const user = buildUser(); - const channelAcl = [] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; - const channelPermission = new ChannelPermission(channel); + describe("channelAclDefinition", () => { + describe("No acl changes", () => { + it("should return true if no acl changes, regardless of role", () => { + const user = buildUser({ orgId: "aaa", groups: [] }); + const channelAcl = buildCompleteAcl(); - expect(channelPermission.canPostToChannel(user)).toBe(false); - }); + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); - it("returns false if user not logged in and channel permissions are empty", async () => { - const user = buildUser({ username: null }); - const channelAcl = [] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: buildCompleteAcl(), + }; - const channelPermission = new ChannelPermission(channel); + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + true + ); + }); - expect(channelPermission.canPostToChannel(user)).toBe(false); - }); - }); + it("should return true if no acl changes, just rearranged, regardless of role", () => { + const user = buildUser({ orgId: "aaa", groups: [] }); + const channelAcl = [ + { category: AclCategory.ANONYMOUS_USER, role: Role.READ }, + { category: AclCategory.AUTHENTICATED_USER, role: Role.READ }, + { + category: AclCategory.GROUP, + subCategory: AclSubCategory.ADMIN, + key: groupId1, + role: Role.OWNER, + }, + { + category: AclCategory.GROUP, + subCategory: AclSubCategory.MEMBER, + key: groupId1, + role: Role.READ, + }, + { + category: AclCategory.ORG, + subCategory: AclSubCategory.ADMIN, + key: orgId1, + role: Role.OWNER, + }, + { + category: AclCategory.ORG, + subCategory: AclSubCategory.MEMBER, + key: orgId1, + role: Role.READ, + }, + ]; - describe("Anonymous User Permissions", () => { - it(`returns true if anonymous permission defined and role is allowed`, () => { - const user = buildUser({ username: null }); + const updatedAcl = [ + { + category: AclCategory.GROUP, + subCategory: AclSubCategory.MEMBER, + key: groupId1, + role: Role.READ, + }, + { category: AclCategory.AUTHENTICATED_USER, role: Role.READ }, + { + category: AclCategory.GROUP, + subCategory: AclSubCategory.ADMIN, + key: groupId1, + role: Role.OWNER, + }, + { + category: AclCategory.ORG, + subCategory: AclSubCategory.MEMBER, + key: orgId1, + role: Role.READ, + }, + { + category: AclCategory.ORG, + subCategory: AclSubCategory.ADMIN, + key: orgId1, + role: Role.OWNER, + }, + { category: AclCategory.ANONYMOUS_USER, role: Role.READ }, + ]; - ALLOWED_ROLES_FOR_POSTING.forEach((allowedRole) => { - const channelAcl = [ - { category: AclCategory.ANONYMOUS_USER, role: allowedRole }, - ] as IChannelAclPermission[]; const channel = { channelAcl, creator: "foo" } as IChannel; - const channelPermission = new ChannelPermission(channel); - expect(channelPermission.canPostToChannel(user)).toBe(true); + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + true + ); }); }); - it("returns false if anonymous permission defined but role is read", () => { - const user = buildUser({ username: null }); - const channelAcl = [ - { category: AclCategory.ANONYMOUS_USER, role: Role.READ }, - ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + describe("OWNER added or removed", () => { + const allowedRoles = [Role.OWNER]; + const notAllowedRoles = [ + Role.READ, + Role.WRITE, + Role.READWRITE, + Role.MODERATE, + Role.MANAGE, + ]; + + allowedRoles.forEach((allowedRole) => { + it(`should return true if user has role: ${allowedRole} and owner is removed`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: allowedRole, // allows change + }, + { category: AclCategory.USER, key: "aaa", role: Role.OWNER }, // will be removed + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: allowedRole, + }, + ] as IChannelAclPermission[]; - const channelPermission = new ChannelPermission(channel); + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); - expect(channelPermission.canPostToChannel(user)).toBe(false); - }); - }); + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; - describe("Authenticated User Permissions", () => { - it(`returns true if authenticated permission defined, user logged in, and role is allowed`, async () => { + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + true + ); + }); + + it(`should return true if user has role: ${allowedRole} and owner is added`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: allowedRole, // allows change + }, + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: allowedRole, + }, + { category: AclCategory.USER, key: "bbb", role: Role.OWNER }, // added + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + true + ); + }); + + it(`should return true if user has role: ${allowedRole} and owner is added in another category`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: allowedRole, // allows change + }, + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: allowedRole, + }, + { + // added + category: AclCategory.GROUP, + subCategory: AclSubCategory.ADMIN, + key: "group_id", + role: Role.OWNER, + }, + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + true + ); + }); + }); + + notAllowedRoles.forEach((notAllowedRole) => { + it(`should return false if user has role: ${notAllowedRole} and owner is removed`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, // DOES NOT ALLOW CHANGE + }, + { category: AclCategory.USER, key: "aaa", role: Role.OWNER }, // will be removed + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, + }, + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + false + ); + }); + + it(`should return false if user has role: ${notAllowedRole} and owner is added`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, // DOES NOT ALLOW CHANGE + }, + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, + }, + { category: AclCategory.USER, key: "bbb", role: Role.OWNER }, // added + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + false + ); + }); + }); + }); + + describe("MANAGER added or removed", () => { + const allowedRoles = [Role.OWNER, Role.MANAGE]; + const notAllowedRoles = [ + Role.READ, + Role.WRITE, + Role.READWRITE, + Role.MODERATE, + ]; + + allowedRoles.forEach((allowedRole) => { + it(`should return true if user has role: ${allowedRole} and manager is removed`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: allowedRole, // allows change + }, + { category: AclCategory.USER, key: "aaa", role: Role.MANAGE }, // will be removed + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: allowedRole, + }, + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + true + ); + }); + + it(`should return true if user has role: ${allowedRole} and manager is added`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: allowedRole, // allows change + }, + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: allowedRole, + }, + { category: AclCategory.USER, key: "bbb", role: Role.MANAGE }, // added + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + true + ); + }); + + it(`should return true if user has role: ${allowedRole} and manager is added in another category`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: allowedRole, // allows change + }, + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: allowedRole, + }, + { + // added + category: AclCategory.GROUP, + subCategory: AclSubCategory.ADMIN, + key: "group_id", + role: Role.MANAGE, + }, + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + true + ); + }); + }); + + notAllowedRoles.forEach((notAllowedRole) => { + it(`should return false if user has role: ${notAllowedRole} and manager is removed`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, // DOES NOT ALLOW CHANGE + }, + { category: AclCategory.USER, key: "aaa", role: Role.MANAGE }, // will be removed + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, + }, + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + false + ); + }); + + it(`should return false if user has role: ${notAllowedRole} and manager is added`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, // DOES NOT ALLOW CHANGE + }, + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, + }, + { category: AclCategory.USER, key: "bbb", role: Role.MANAGE }, // added + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + false + ); + }); + }); + }); + + describe("MODERATOR added or removed", () => { + const allowedRoles = [Role.OWNER, Role.MANAGE]; + const notAllowedRoles = [ + Role.READ, + Role.WRITE, + Role.READWRITE, + Role.MODERATE, + ]; + + allowedRoles.forEach((allowedRole) => { + it(`should return true if user has role: ${allowedRole} and moderator is removed`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: allowedRole, // allows change + }, + { category: AclCategory.USER, key: "aaa", role: Role.MODERATE }, // will be removed + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: allowedRole, + }, + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + true + ); + }); + + it(`should return true if user has role: ${allowedRole} and moderator is added`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: allowedRole, // allows change + }, + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: allowedRole, + }, + { category: AclCategory.USER, key: "bbb", role: Role.MODERATE }, // added + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + true + ); + }); + + it(`should return true if user has role: ${allowedRole} and moderator is added in another category`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: allowedRole, // allows change + }, + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: allowedRole, + }, + { + // added + category: AclCategory.GROUP, + subCategory: AclSubCategory.ADMIN, + key: "group_id", + role: Role.MODERATE, + }, + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + true + ); + }); + }); + + notAllowedRoles.forEach((notAllowedRole) => { + it(`should return false if user has role: ${notAllowedRole} and moderator is removed`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, // DOES NOT ALLOW CHANGE + }, + { category: AclCategory.USER, key: "aaa", role: Role.MODERATE }, // will be removed + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, + }, + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + false + ); + }); + + it(`should return false if user has role: ${notAllowedRole} and moderator is added`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, // DOES NOT ALLOW CHANGE + }, + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, + }, + { category: AclCategory.USER, key: "bbb", role: Role.MODERATE }, // added + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + false + ); + }); + }); + }); + + describe("ORG added or removed or updated", () => { + const allowedRoles = [Role.OWNER, Role.MANAGE]; + const notAllowedRoles = [ + Role.READ, + Role.WRITE, + Role.READWRITE, + Role.MODERATE, + ]; + + allowedRoles.forEach((allowedRole) => { + it(`should return true if user has role: ${allowedRole} and org is removed`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: allowedRole, // allows change + }, + { + // will be removed + category: AclCategory.ORG, + subCategory: AclSubCategory.ADMIN, + key: "aaa", + role: Role.MODERATE, + }, + { + // will be removed + category: AclCategory.ORG, + subCategory: AclSubCategory.MEMBER, + key: "aaa", + role: Role.READ, + }, + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: allowedRole, + }, + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + true + ); + }); + + it(`should return true if user has role: ${allowedRole} and org is added`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: allowedRole, // allows change + }, + { + category: AclCategory.ORG, + subCategory: AclSubCategory.ADMIN, + key: "aaa", + role: Role.MODERATE, + }, + { + category: AclCategory.ORG, + subCategory: AclSubCategory.MEMBER, + key: "aaa", + role: Role.READ, + }, + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: allowedRole, + }, + { + // no update + category: AclCategory.ORG, + subCategory: AclSubCategory.ADMIN, + key: "aaa", + role: Role.MODERATE, + }, + { + // no update + category: AclCategory.ORG, + subCategory: AclSubCategory.MEMBER, + key: "aaa", + role: Role.READ, + }, + { + // added + category: AclCategory.ORG, + subCategory: AclSubCategory.ADMIN, + key: "bbb", + role: Role.MODERATE, + }, + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + true + ); + }); + + it(`should return true if user has role: ${allowedRole} and org role is updated`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: allowedRole, // allows change + }, + { + category: AclCategory.ORG, + subCategory: AclSubCategory.ADMIN, + key: "aaa", + role: Role.MODERATE, + }, + { + category: AclCategory.ORG, + subCategory: AclSubCategory.MEMBER, + key: "aaa", + role: Role.READ, + }, + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: allowedRole, + }, + { + // no update + category: AclCategory.ORG, + subCategory: AclSubCategory.ADMIN, + key: "aaa", + role: Role.MODERATE, + }, + { + category: AclCategory.ORG, + subCategory: AclSubCategory.MEMBER, + key: "aaa", + role: Role.READWRITE, // role changed + }, + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + true + ); + }); + }); + + notAllowedRoles.forEach((notAllowedRole) => { + it(`should return false if user has role: ${notAllowedRole} and org is removed`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, // DOES NOT ALLOW CHANGE + }, + { + // will be removed + category: AclCategory.ORG, + subCategory: AclSubCategory.ADMIN, + key: "aaa", + role: Role.MODERATE, + }, + { + // will be removed + category: AclCategory.ORG, + subCategory: AclSubCategory.MEMBER, + key: "aaa", + role: Role.READ, + }, + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, + }, + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + false + ); + }); + + it(`should return false if user has role: ${notAllowedRole} and org is added`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, // DOES NOT ALLOW CHANGE + }, + { + category: AclCategory.ORG, + subCategory: AclSubCategory.ADMIN, + key: "aaa", + role: Role.MODERATE, + }, + { + category: AclCategory.ORG, + subCategory: AclSubCategory.MEMBER, + key: "aaa", + role: Role.READ, + }, + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, + }, + { + // no update + category: AclCategory.ORG, + subCategory: AclSubCategory.ADMIN, + key: "aaa", + role: Role.MODERATE, + }, + { + // no update + category: AclCategory.ORG, + subCategory: AclSubCategory.MEMBER, + key: "aaa", + role: Role.READ, + }, + { + // added + category: AclCategory.ORG, + subCategory: AclSubCategory.ADMIN, + key: "bbb", + role: Role.MODERATE, + }, + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + false + ); + }); + + it(`should return false if user has role: ${notAllowedRole} and org role is updated`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, // DOES NOT ALLOW CHANGE + }, + { + category: AclCategory.ORG, + subCategory: AclSubCategory.ADMIN, + key: "aaa", + role: Role.MODERATE, + }, + { + category: AclCategory.ORG, + subCategory: AclSubCategory.MEMBER, + key: "aaa", + role: Role.READ, + }, + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, + }, + { + // no update + category: AclCategory.ORG, + subCategory: AclSubCategory.ADMIN, + key: "aaa", + role: Role.MODERATE, + }, + { + category: AclCategory.ORG, + subCategory: AclSubCategory.MEMBER, + key: "aaa", + role: Role.READWRITE, // role changed + }, + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + false + ); + }); + }); + }); + + describe("GROUP added or removed or updated", () => { + const allowedRoles = [Role.OWNER, Role.MANAGE]; + const notAllowedRoles = [ + Role.READ, + Role.WRITE, + Role.READWRITE, + Role.MODERATE, + ]; + + allowedRoles.forEach((allowedRole) => { + it(`should return true if user has role: ${allowedRole} and group is removed`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: allowedRole, // allows change + }, + { + // will be removed + category: AclCategory.GROUP, + subCategory: AclSubCategory.ADMIN, + key: "aaa", + role: Role.MODERATE, + }, + { + // will be removed + category: AclCategory.GROUP, + subCategory: AclSubCategory.MEMBER, + key: "aaa", + role: Role.READ, + }, + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: allowedRole, + }, + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + true + ); + }); + + it(`should return true if user has role: ${allowedRole} and GROUP is added`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: allowedRole, // allows change + }, + { + category: AclCategory.GROUP, + subCategory: AclSubCategory.ADMIN, + key: "aaa", + role: Role.MODERATE, + }, + { + category: AclCategory.GROUP, + subCategory: AclSubCategory.MEMBER, + key: "aaa", + role: Role.READ, + }, + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: allowedRole, + }, + { + // no update + category: AclCategory.GROUP, + subCategory: AclSubCategory.ADMIN, + key: "aaa", + role: Role.MODERATE, + }, + { + // no update + category: AclCategory.GROUP, + subCategory: AclSubCategory.MEMBER, + key: "aaa", + role: Role.READ, + }, + { + // added + category: AclCategory.GROUP, + subCategory: AclSubCategory.ADMIN, + key: "bbb", + role: Role.MODERATE, + }, + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + true + ); + }); + + it(`should return true if user has role: ${allowedRole} and GROUP role is updated`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: allowedRole, // allows change + }, + { + category: AclCategory.GROUP, + subCategory: AclSubCategory.ADMIN, + key: "aaa", + role: Role.MODERATE, + }, + { + category: AclCategory.GROUP, + subCategory: AclSubCategory.MEMBER, + key: "aaa", + role: Role.READ, + }, + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: allowedRole, + }, + { + // no update + category: AclCategory.GROUP, + subCategory: AclSubCategory.ADMIN, + key: "aaa", + role: Role.MODERATE, + }, + { + category: AclCategory.GROUP, + subCategory: AclSubCategory.MEMBER, + key: "aaa", + role: Role.READWRITE, // role changed + }, + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + true + ); + }); + }); + + notAllowedRoles.forEach((notAllowedRole) => { + it(`should return false if user has role: ${notAllowedRole} and GROUP is removed`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, // DOES NOT ALLOW CHANGE + }, + { + // will be removed + category: AclCategory.GROUP, + subCategory: AclSubCategory.ADMIN, + key: "aaa", + role: Role.MODERATE, + }, + { + // will be removed + category: AclCategory.GROUP, + subCategory: AclSubCategory.MEMBER, + key: "aaa", + role: Role.READ, + }, + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, + }, + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + false + ); + }); + + it(`should return false if user has role: ${notAllowedRole} and GROUP is added`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, // DOES NOT ALLOW CHANGE + }, + { + category: AclCategory.GROUP, + subCategory: AclSubCategory.ADMIN, + key: "aaa", + role: Role.MODERATE, + }, + { + category: AclCategory.GROUP, + subCategory: AclSubCategory.MEMBER, + key: "aaa", + role: Role.READ, + }, + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, + }, + { + // no update + category: AclCategory.GROUP, + subCategory: AclSubCategory.ADMIN, + key: "aaa", + role: Role.MODERATE, + }, + { + // no update + category: AclCategory.GROUP, + subCategory: AclSubCategory.MEMBER, + key: "aaa", + role: Role.READ, + }, + { + // added + category: AclCategory.GROUP, + subCategory: AclSubCategory.ADMIN, + key: "bbb", + role: Role.MODERATE, + }, + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + false + ); + }); + + it(`should return false if user has role: ${notAllowedRole} and GROUP role is updated`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, // DOES NOT ALLOW CHANGE + }, + { + category: AclCategory.GROUP, + subCategory: AclSubCategory.ADMIN, + key: "aaa", + role: Role.MODERATE, + }, + { + category: AclCategory.GROUP, + subCategory: AclSubCategory.MEMBER, + key: "aaa", + role: Role.READ, + }, + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, + }, + { + // no update + category: AclCategory.GROUP, + subCategory: AclSubCategory.ADMIN, + key: "aaa", + role: Role.MODERATE, + }, + { + category: AclCategory.GROUP, + subCategory: AclSubCategory.MEMBER, + key: "aaa", + role: Role.READWRITE, // role changed + }, + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + false + ); + }); + }); + }); + + describe("USER added or removed or updated", () => { + const allowedRoles = [Role.OWNER, Role.MANAGE]; + const notAllowedRoles = [ + Role.READ, + Role.WRITE, + Role.READWRITE, + Role.MODERATE, + ]; + + allowedRoles.forEach((allowedRole) => { + it(`should return true if user has role: ${allowedRole} and USER is removed`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: allowedRole, // allows change + }, + { category: AclCategory.USER, key: "aaa", role: Role.READ }, // will be removed + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: allowedRole, + }, + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + true + ); + }); + + it(`should return true if user has role: ${allowedRole} and USER is added`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: allowedRole, // allows change + }, + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: allowedRole, + }, + { category: AclCategory.USER, key: "aaa", role: Role.READ }, // added + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + true + ); + }); + + it(`should return true if user has role: ${allowedRole} and USER role is updated`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: allowedRole, // allows change + }, + { category: AclCategory.USER, key: "aaa", role: Role.READ }, // existing + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: allowedRole, + }, + { category: AclCategory.USER, key: "aaa", role: Role.READWRITE }, // role changed + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + true + ); + }); + }); + + notAllowedRoles.forEach((notAllowedRole) => { + it(`should return false if user has role: ${notAllowedRole} and USER is removed`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, // DOES NOT ALLOW CHANGE + }, + { category: AclCategory.USER, key: "aaa", role: Role.READ }, // will be removed + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, + }, + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + false + ); + }); + + it(`should return false if user has role: ${notAllowedRole} and USER is added`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, // DOES NOT ALLOW CHANGE + }, + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, + }, + { category: AclCategory.USER, key: "aaa", role: Role.READ }, // added + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + false + ); + }); + + it(`should return false if user has role: ${notAllowedRole} and USER role is updated`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, // DOES NOT ALLOW CHANGE + }, + { category: AclCategory.USER, key: "aaa", role: Role.READ }, // existing + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, + }, + { category: AclCategory.USER, key: "aaa", role: Role.READWRITE }, // role changed + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + false + ); + }); + }); + }); + + describe("AUTHENTICATED_USER added or removed or updated", () => { + const allowedRoles = [Role.OWNER, Role.MANAGE]; + const notAllowedRoles = [ + Role.READ, + Role.WRITE, + Role.READWRITE, + Role.MODERATE, + ]; + + allowedRoles.forEach((allowedRole) => { + it(`should return true if user has role: ${allowedRole} and AUTHENTICATED_USER is removed`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: allowedRole, // allows change + }, + { category: AclCategory.AUTHENTICATED_USER, role: Role.READ }, // will be removed + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: allowedRole, + }, + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + true + ); + }); + + it(`should return true if user has role: ${allowedRole} and AUTHENTICATED_USER is added`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: allowedRole, // allows change + }, + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: allowedRole, + }, + { category: AclCategory.AUTHENTICATED_USER, role: Role.READ }, // added + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + true + ); + }); + + it(`should return true if user has role: ${allowedRole} and AUTHENTICATED_USER role is updated`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: allowedRole, // allows change + }, + { category: AclCategory.AUTHENTICATED_USER, role: Role.READ }, + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: allowedRole, + }, + { + category: AclCategory.AUTHENTICATED_USER, + role: Role.READWRITE, // role changed + }, + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + true + ); + }); + }); + + notAllowedRoles.forEach((notAllowedRole) => { + it(`should return false if user has role: ${notAllowedRole} and AUTHENTICATED_USER is removed`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, // DOES NOT ALLOW CHANGE + }, + { category: AclCategory.AUTHENTICATED_USER, role: Role.READ }, // will be removed + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, + }, + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + false + ); + }); + + it(`should return false if user has role: ${notAllowedRole} and AUTHENTICATED_USER is added`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, // DOES NOT ALLOW CHANGE + }, + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, + }, + { category: AclCategory.AUTHENTICATED_USER, role: Role.READ }, // added + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + false + ); + }); + + it(`should return false if user has role: ${notAllowedRole} and AUTHENTICATED_USER role is updated`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, // DOES NOT ALLOW CHANGE + }, + { category: AclCategory.AUTHENTICATED_USER, role: Role.READ }, // existing + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, + }, + { + category: AclCategory.AUTHENTICATED_USER, + role: Role.READWRITE, // role changed + }, + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + false + ); + }); + }); + }); + + describe("ANONYMOUS_USER added or removed or updated", () => { + const allowedRoles = [Role.OWNER, Role.MANAGE]; + const notAllowedRoles = [ + Role.READ, + Role.WRITE, + Role.READWRITE, + Role.MODERATE, + ]; + + allowedRoles.forEach((allowedRole) => { + it(`should return true if user has role: ${allowedRole} and ANONYMOUS_USER is removed`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: allowedRole, // allows change + }, + { category: AclCategory.ANONYMOUS_USER, role: Role.READ }, // will be removed + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: allowedRole, + }, + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + true + ); + }); + + it(`should return true if user has role: ${allowedRole} and ANONYMOUS_USER is added`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: allowedRole, // allows change + }, + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: allowedRole, + }, + { category: AclCategory.ANONYMOUS_USER, role: Role.READ }, // added + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + true + ); + }); + + it(`should return true if user has role: ${allowedRole} and ANONYMOUS_USER role is updated`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: allowedRole, // allows change + }, + { category: AclCategory.ANONYMOUS_USER, role: Role.READ }, // existing + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: allowedRole, + }, + { category: AclCategory.ANONYMOUS_USER, role: Role.READWRITE }, // role changed + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + true + ); + }); + }); + + notAllowedRoles.forEach((notAllowedRole) => { + it(`should return false if user has role: ${notAllowedRole} and ANONYMOUS_USER is removed`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, // DOES NOT ALLOW CHANGE + }, + { category: AclCategory.ANONYMOUS_USER, role: Role.READ }, // will be removed + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, + }, + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + false + ); + }); + + it(`should return false if user has role: ${notAllowedRole} and ANONYMOUS_USER is added`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, // DOES NOT ALLOW CHANGE + }, + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, + }, + { category: AclCategory.ANONYMOUS_USER, role: Role.READ }, // added + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + false + ); + }); + + it(`should return false if user has role: ${notAllowedRole} and ANONYMOUS_USER role is updated`, () => { + const user = buildUser(); + const channelAcl = [ + { + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, // DOES NOT ALLOW CHANGE + }, + { category: AclCategory.ANONYMOUS_USER, role: Role.READ }, // existing + ] as IChannelAclPermission[]; + const updatedAcl = [ + { + // no update + category: AclCategory.USER, + key: user.username, + role: notAllowedRole, + }, + { category: AclCategory.ANONYMOUS_USER, role: Role.READWRITE }, // role changed + ] as IChannelAclPermission[]; + + const channel = { channelAcl, creator: "foo" } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + // TODO: remove ts-ignore when V2 interfaces are hoisted from service to hub.js + // @ts-ignore + channelAclDefinition: updatedAcl, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + false + ); + }); + }); + }); + }); + + describe("defaultPostStatus", () => { + it("returns true if value not changed regardless of role", () => { const user = buildUser(); + const channelAcl = [ + { category: AclCategory.USER, key: user.username, role: Role.READ }, // bad role for update + ] as IChannelAclPermission[]; - ALLOWED_ROLES_FOR_POSTING.forEach((allowedRole) => { - const channelAcl = [ - { category: AclCategory.AUTHENTICATED_USER, role: allowedRole }, - ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + defaultPostStatus: PostStatus.APPROVED, + channelAcl, + creator: "foo", + } as IChannel; + const channelPermission = new ChannelPermission(channel); - const channelPermission = new ChannelPermission(channel); + const updates: IUpdateChannel = { + defaultPostStatus: PostStatus.APPROVED, + }; - expect(channelPermission.canPostToChannel(user)).toBe(true); - }); + expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); }); - it("returns false if authenticated permission defined, user logged in, and role is read", async () => { + it("returns true if updated is undefined regardless of role", () => { const user = buildUser(); const channelAcl = [ - { category: AclCategory.AUTHENTICATED_USER, role: Role.READ }, + { category: AclCategory.USER, key: user.username, role: Role.READ }, // bad role for update ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + defaultPostStatus: PostStatus.APPROVED, + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); - expect(channelPermission.canPostToChannel(user)).toBe(false); + const updates: IUpdateChannel = { + defaultPostStatus: undefined, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); }); - it("returns false if authenticated permission defined and user is not logged in", async () => { - const user = buildUser({ username: null }); + it("returns true if user has a role of moderate", () => { + const user = buildUser(); const channelAcl = [ - { category: AclCategory.AUTHENTICATED_USER, role: Role.READWRITE }, + { + category: AclCategory.USER, + key: user.username, + role: Role.MODERATE, + }, ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + defaultPostStatus: PostStatus.PENDING, + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); - expect(channelPermission.canPostToChannel(user)).toBe(false); + const updates: IUpdateChannel = { + defaultPostStatus: PostStatus.APPROVED, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); }); - }); - describe("Group Permissions", () => { - it("returns true if user is group member in group permission list and role is allowed", async () => { - ALLOWED_ROLES_FOR_POSTING.forEach((allowedRole) => { - const channelAcl = [ - { - category: AclCategory.GROUP, - subCategory: AclSubCategory.MEMBER, - key: groupId1, - role: allowedRole, // members write - }, - { - category: AclCategory.GROUP, - subCategory: AclSubCategory.ADMIN, - key: groupId1, - role: Role.READ, - }, - ] as IChannelAclPermission[]; + it("returns true if user has a role of manage", () => { + const user = buildUser(); + const channelAcl = [ + { category: AclCategory.USER, key: user.username, role: Role.MANAGE }, + ] as IChannelAclPermission[]; - ALLOWED_GROUP_ROLES.forEach((memberType) => { - const user = buildUser({ - orgId: orgId1, - groups: [buildGroup(groupId1, memberType)], // member in groupId1 - }); - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + defaultPostStatus: PostStatus.PENDING, + channelAcl, + creator: "foo", + } as IChannel; + const channelPermission = new ChannelPermission(channel); - const channelPermission = new ChannelPermission(channel); + const updates: IUpdateChannel = { + defaultPostStatus: PostStatus.APPROVED, + }; - expect(channelPermission.canPostToChannel(user)).toBe(true); - }); - }); + expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); }); - it("returns false if user is group member in group permission list and role is NOT allowed", async () => { - const user = buildUser(); // member in groupId1 + it("returns true if user has a role of owner", () => { + const user = buildUser(); + const channelAcl = [ + { category: AclCategory.USER, key: user.username, role: Role.OWNER }, + ] as IChannelAclPermission[]; + + const channel = { + defaultPostStatus: PostStatus.PENDING, + channelAcl, + creator: "foo", + } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + defaultPostStatus: PostStatus.APPROVED, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); + }); + + it("returns false if user does not have a minimum role of moderate", () => { + const user = buildUser(); const channelAcl = [ { - category: AclCategory.GROUP, - subCategory: AclSubCategory.MEMBER, - key: groupId1, - role: Role.READ, // members read + category: AclCategory.USER, + key: user.username, + role: Role.READWRITE, }, + ] as IChannelAclPermission[]; + + const channel = { + defaultPostStatus: PostStatus.PENDING, + channelAcl, + creator: "foo", + } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + defaultPostStatus: PostStatus.APPROVED, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + false + ); + }); + }); + + describe("blockWords", () => { + it("returns true if value not changed from existing regardless of role", () => { + const user = buildUser(); + const channelAcl = [ + { category: AclCategory.USER, key: user.username, role: Role.READ }, // bad role for update + ] as IChannelAclPermission[]; + + const channel = { + blockWords: ["burrito"], + channelAcl, + creator: "foo", + } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + blockWords: ["burrito"], + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); + }); + + it("returns true if value not changed (just rearranged) from existing regardless of role", () => { + const user = buildUser(); + const channelAcl = [ + { category: AclCategory.USER, key: user.username, role: Role.READ }, // bad role for update + ] as IChannelAclPermission[]; + + const channel = { + blockWords: ["burrito", "taco"], + channelAcl, + creator: "foo", + } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + blockWords: ["taco", "burrito"], + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); + }); + + it("returns true if user has a role of moderate and blockWord is removed", () => { + const user = buildUser(); + const channelAcl = [ { - category: AclCategory.GROUP, - subCategory: AclSubCategory.ADMIN, - key: groupId1, - role: Role.READ, + category: AclCategory.USER, + key: user.username, + role: Role.MODERATE, }, ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + blockWords: ["burrito", "taco"], + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); - expect(channelPermission.canPostToChannel(user)).toBe(false); + const updates: IUpdateChannel = { + blockWords: ["burrito"], + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); }); - it("returns false if user is group member in group permission list, role is allowed, but userMemberType is none", async () => { - const user = buildUser({ - groups: [buildGroup(groupId1, "none")], // none in groupId1 - }); + it("returns true if user has a role of moderate and blockWord is added", () => { + const user = buildUser(); const channelAcl = [ { - category: AclCategory.GROUP, - subCategory: AclSubCategory.MEMBER, - key: groupId1, - role: Role.READWRITE, // members read + category: AclCategory.USER, + key: user.username, + role: Role.MODERATE, }, + ] as IChannelAclPermission[]; + + const channel = { + blockWords: ["burrito", "taco"], + channelAcl, + creator: "foo", + } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + blockWords: ["burrito", "taco", "flan"], + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); + }); + + it("returns true if user has a role of manage", () => { + const user = buildUser(); + const channelAcl = [ + { category: AclCategory.USER, key: user.username, role: Role.MANAGE }, + ] as IChannelAclPermission[]; + + const channel = { + blockWords: ["burrito"], + channelAcl, + creator: "foo", + } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + blockWords: ["taco"], + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); + }); + + it("returns true if user has a role of owner", () => { + const user = buildUser(); + const channelAcl = [ + { category: AclCategory.USER, key: user.username, role: Role.OWNER }, + ] as IChannelAclPermission[]; + + const channel = { + blockWords: ["burrito"], + channelAcl, + creator: "foo", + } as IChannel; + const channelPermission = new ChannelPermission(channel); + + const updates: IUpdateChannel = { + blockWords: ["taco"], + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); + }); + + it("returns false if user does not have a minimum role of moderate", () => { + const user = buildUser(); + const channelAcl = [ { - category: AclCategory.GROUP, - subCategory: AclSubCategory.ADMIN, - key: groupId1, - role: Role.READWRITE, // admins read + category: AclCategory.USER, + key: user.username, + role: Role.READWRITE, }, ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + blockWords: ["burrito"], + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); - expect(channelPermission.canPostToChannel(user)).toBe(false); - }); + const updates: IUpdateChannel = { + blockWords: ["taco"], + }; - it("returns true if user is group owner/admin in group permission list and role is allowed", async () => { - ALLOWED_ROLES_FOR_POSTING.forEach((allowedRole) => { - const channelAcl = [ - { - category: AclCategory.GROUP, - subCategory: AclSubCategory.MEMBER, - key: groupId1, - role: Role.READ, - }, - { - category: AclCategory.GROUP, - subCategory: AclSubCategory.ADMIN, - key: groupId1, - role: allowedRole, // admins write - }, - ] as IChannelAclPermission[]; + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + false + ); + }); + }); - ["owner", "admin"].forEach((memberType) => { - const user = buildUser({ - orgId: orgId1, - groups: [buildGroup(groupId1, memberType)], // admin in groupId1 - }); - const channel = { channelAcl, creator: "foo" } as IChannel; + describe("softDelete", () => { + it("returns true if value not changed from existing regardless of role", () => { + const user = buildUser(); + const channelAcl = [ + { category: AclCategory.USER, key: user.username, role: Role.READ }, // bad role for update + ] as IChannelAclPermission[]; - const channelPermission = new ChannelPermission(channel); + const channel = { + softDelete: true, + channelAcl, + creator: "foo", + } as IChannel; + const channelPermission = new ChannelPermission(channel); - expect(channelPermission.canPostToChannel(user)).toBe(true); - }); - }); + const updates: IUpdateChannel = { + softDelete: true, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); }); - it("returns false if user is group owner/admin in group permission list and role is NOT allowed", async () => { - const user = buildUser(); // admin in groupId2 + it("returns true if update is undefined regardless of role", () => { + const user = buildUser(); const channelAcl = [ - { - category: AclCategory.GROUP, - subCategory: AclSubCategory.MEMBER, - key: groupId2, - role: Role.READ, - }, - { - category: AclCategory.GROUP, - subCategory: AclSubCategory.ADMIN, - key: groupId2, - role: Role.READ, // admins read - }, + { category: AclCategory.USER, key: user.username, role: Role.READ }, // bad role for update ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + softDelete: true, + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); - expect(channelPermission.canPostToChannel(user)).toBe(false); + const updates: IUpdateChannel = { + softDelete: undefined, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); }); - it("returns true if user is group member of at least one group in permissions list that is discussable", async () => { - const user = buildUser({ - orgId: orgId1, - groups: [ - buildGroup(groupId1, "member"), // member in groupId1 - buildGroup(groupId2, "member", [CANNOT_DISCUSS]), // member in groupId2 - ], - }); + it("returns true if user has a role of manage", () => { + const user = buildUser(); const channelAcl = [ - { - category: AclCategory.GROUP, - subCategory: AclSubCategory.MEMBER, - key: groupId1, - role: Role.READWRITE, // members write - }, - { - category: AclCategory.GROUP, - subCategory: AclSubCategory.ADMIN, - key: groupId1, - role: Role.READ, - }, - { - category: AclCategory.GROUP, - subCategory: AclSubCategory.MEMBER, - key: groupId2, - role: Role.READWRITE, // members write, group CANNOT_DISCUSS - }, - { - category: AclCategory.GROUP, - subCategory: AclSubCategory.ADMIN, - key: groupId2, - role: Role.READ, - }, + { category: AclCategory.USER, key: user.username, role: Role.MANAGE }, ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + softDelete: false, + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); - expect(channelPermission.canPostToChannel(user)).toBe(true); + const updates: IUpdateChannel = { + softDelete: true, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); }); - it("returns false if user is group member in permissions list but the group is not discussable", async () => { - const user = buildUser({ - orgId: orgId1, - groups: [ - buildGroup(groupId1, "member", [CANNOT_DISCUSS]), // member in groupId1 - ], - }); + it("returns true if user has a role of owner", () => { + const user = buildUser(); const channelAcl = [ - { - category: AclCategory.GROUP, - subCategory: AclSubCategory.MEMBER, - key: groupId1, - role: Role.READWRITE, // members write - }, - { - category: AclCategory.GROUP, - subCategory: AclSubCategory.ADMIN, - key: groupId1, - role: Role.READ, - }, + { category: AclCategory.USER, key: user.username, role: Role.OWNER }, ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + softDelete: false, + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); - expect(channelPermission.canPostToChannel(user)).toBe(false); + const updates: IUpdateChannel = { + softDelete: true, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); }); - it("returns false if user is group admin but group is not in permissions list", async () => { - const user = buildUser({ - orgId: orgId1, - groups: [ - buildGroup("unknownGroupId", "admin"), // admin in unknownGroupId - ], - }); + it("returns false if user does not have a minimum role of manage", () => { + const user = buildUser(); const channelAcl = [ { - category: AclCategory.GROUP, - subCategory: AclSubCategory.MEMBER, - key: groupId1, - role: Role.READWRITE, // members write - }, - { - category: AclCategory.GROUP, - subCategory: AclSubCategory.ADMIN, - key: groupId1, - role: Role.READWRITE, // admin write + category: AclCategory.USER, + key: user.username, + role: Role.MODERATE, }, ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + softDelete: false, + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); - expect(channelPermission.canPostToChannel(user)).toBe(false); + const updates: IUpdateChannel = { + softDelete: true, + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + false + ); }); }); - describe("Org Permissions", () => { - it("returns true if user is org member in permissions list and member role is allowed", async () => { + describe("name", () => { + it("returns true if value not changed from existing regardless of role", () => { const user = buildUser(); + const channelAcl = [ + { category: AclCategory.USER, key: user.username, role: Role.READ }, // bad role for update + ] as IChannelAclPermission[]; - ALLOWED_ROLES_FOR_POSTING.forEach((allowedRole) => { - const channelAcl = [ - { - category: AclCategory.ORG, - subCategory: AclSubCategory.MEMBER, - key: user.orgId, - role: allowedRole, // members write - }, - { - category: AclCategory.ORG, - subCategory: AclSubCategory.ADMIN, - key: user.orgId, - role: Role.READ, // admin read - }, - ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "joker" } as IChannel; + const channel = { + name: "burrito", + channelAcl, + creator: "foo", + } as IChannel; + const channelPermission = new ChannelPermission(channel); - const channelPermission = new ChannelPermission(channel); + const updates: IUpdateChannel = { + name: "burrito", + }; - expect(channelPermission.canPostToChannel(user)).toBe(true); - }); + expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); }); - it("returns true if user is org_admin in permissions list and admin role is allowed", async () => { - const user = buildUser({ role: "org_admin" }); + it("returns true if update is undefined regardless of role", () => { + const user = buildUser(); + const channelAcl = [ + { category: AclCategory.USER, key: user.username, role: Role.READ }, // bad role for update + ] as IChannelAclPermission[]; - ALLOWED_ROLES_FOR_POSTING.forEach((allowedRole) => { - const channelAcl = [ - { - category: AclCategory.ORG, - subCategory: AclSubCategory.MEMBER, - key: user.orgId, - role: Role.READ, // members read - }, - { - category: AclCategory.ORG, - subCategory: AclSubCategory.ADMIN, - key: user.orgId, - role: allowedRole, // admin write - }, - ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + name: "burrito", + channelAcl, + creator: "foo", + } as IChannel; + const channelPermission = new ChannelPermission(channel); - const channelPermission = new ChannelPermission(channel); + const updates: IUpdateChannel = { + name: undefined, + }; - expect(channelPermission.canPostToChannel(user)).toBe(true); - }); + expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); }); - it("returns false if user is not in the permissions org", async () => { - const user = buildUser({ orgId: "unknownOrgId" }); + it("returns true if user has a role of manage", () => { + const user = buildUser(); const channelAcl = [ - { - category: AclCategory.ORG, - subCategory: AclSubCategory.MEMBER, - key: orgId1, - role: Role.READ, // members read - }, - { - category: AclCategory.ORG, - subCategory: AclSubCategory.ADMIN, - key: orgId1, - role: Role.READWRITE, // admin write - }, + { category: AclCategory.USER, key: user.username, role: Role.MANAGE }, ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + name: "burrito", + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); - expect(channelPermission.canPostToChannel(user)).toBe(false); + const updates: IUpdateChannel = { + name: "foo", + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); }); - }); - describe("User Permissions", () => { - it("returns true if user is in permissions list and role is allowed", () => { + it("returns true if user has a role of owner", () => { const user = buildUser(); + const channelAcl = [ + { category: AclCategory.USER, key: user.username, role: Role.OWNER }, + ] as IChannelAclPermission[]; - ALLOWED_ROLES_FOR_POSTING.forEach((allowedRole) => { - const channelAcl = [ - { - category: AclCategory.USER, - key: user.username, - role: allowedRole, - }, - ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + name: "burrito", + channelAcl, + creator: "foo", + } as IChannel; + const channelPermission = new ChannelPermission(channel); - const channelPermission = new ChannelPermission(channel); + const updates: IUpdateChannel = { + name: "foo", + }; - expect(channelPermission.canPostToChannel(user)).toBe(true); - }); + expect(channelPermission.canUpdateProperties(user, updates)).toBe(true); }); - it("returns false if user is in permissions list but role is read", () => { + it("returns false if user does not have a minimum role of manage", () => { const user = buildUser(); const channelAcl = [ - { category: AclCategory.USER, key: user.username, role: Role.READ }, + { + category: AclCategory.USER, + key: user.username, + role: Role.MODERATE, + }, ] as IChannelAclPermission[]; - const channel = { channelAcl, creator: "foo" } as IChannel; + const channel = { + name: "burrito", + channelAcl, + creator: "foo", + } as IChannel; const channelPermission = new ChannelPermission(channel); - expect(channelPermission.canPostToChannel(user)).toBe(false); + const updates: IUpdateChannel = { + name: "foo", + }; + + expect(channelPermission.canUpdateProperties(user, updates)).toBe( + false + ); }); }); }); diff --git a/packages/discussions/test/utils/channels/can-edit-channel.test.ts b/packages/discussions/test/utils/channels/can-edit-channel.test.ts index 426a5c2462b..da3d0bb5b24 100644 --- a/packages/discussions/test/utils/channels/can-edit-channel.test.ts +++ b/packages/discussions/test/utils/channels/can-edit-channel.test.ts @@ -14,6 +14,7 @@ import * as portalPrivModule from "../../../src/utils/portal-privilege"; describe("canEditChannel", () => { let hasOrgAdminUpdateRightsSpy: jasmine.Spy; let canModerateChannelSpy: jasmine.Spy; + let canUpdatePropertiesSpy: jasmine.Spy; let isAuthorizedToModifyChannelByLegacyPermissionsSpy: jasmine.Spy; beforeAll(() => { @@ -25,6 +26,10 @@ describe("canEditChannel", () => { ChannelPermission.prototype, "canModerateChannel" ); + canUpdatePropertiesSpy = spyOn( + ChannelPermission.prototype, + "canUpdateProperties" + ); isAuthorizedToModifyChannelByLegacyPermissionsSpy = spyOn( isAuthorizedToModifyChannelByLegacyPermissionsModule, "isAuthorizedToModifyChannelByLegacyPermissions" @@ -35,6 +40,7 @@ describe("canEditChannel", () => { hasOrgAdminUpdateRightsSpy.calls.reset(); isAuthorizedToModifyChannelByLegacyPermissionsSpy.calls.reset(); canModerateChannelSpy.calls.reset(); + canUpdatePropertiesSpy.calls.reset(); }); describe("With Org Admin", () => { @@ -51,6 +57,7 @@ describe("canEditChannel", () => { expect(arg2).toBe(channel.orgId); expect(canModerateChannelSpy.calls.count()).toBe(0); + expect(canUpdatePropertiesSpy.calls.count()).toBe(0); expect( isAuthorizedToModifyChannelByLegacyPermissionsSpy.calls.count() ).toBe(0); @@ -58,9 +65,10 @@ describe("canEditChannel", () => { }); describe("With channelAcl Permissions", () => { - it("return true if channelPermission.canModerateChannel is true", () => { + it("return true if channelPermission.canModerateChannel and channelPermission.canUpdateProperties is true", () => { hasOrgAdminUpdateRightsSpy.and.callFake(() => false); canModerateChannelSpy.and.callFake(() => true); + canUpdatePropertiesSpy.and.callFake(() => true); const user = {} as IDiscussionsUser; const channel = { @@ -74,6 +82,10 @@ describe("canEditChannel", () => { const [arg1] = canModerateChannelSpy.calls.allArgs()[0]; // args for 1st call expect(arg1).toBe(user); + expect(canUpdatePropertiesSpy.calls.count()).toBe(1); + const [arg2] = canUpdatePropertiesSpy.calls.allArgs()[0]; // args for 1st call + expect(arg2).toEqual({}); + expect( isAuthorizedToModifyChannelByLegacyPermissionsSpy.calls.count() ).toBe(0); @@ -82,6 +94,7 @@ describe("canEditChannel", () => { it("return false if channelPermission.canModerateChannel is false", () => { hasOrgAdminUpdateRightsSpy.and.callFake(() => false); canModerateChannelSpy.and.callFake(() => false); + canUpdatePropertiesSpy.and.callFake(() => true); const user = {} as IDiscussionsUser; const channel = { @@ -95,14 +108,17 @@ describe("canEditChannel", () => { const [arg1] = canModerateChannelSpy.calls.allArgs()[0]; // args for 1st call expect(arg1).toBe(user); + expect(canUpdatePropertiesSpy.calls.count()).toBe(0); + expect( isAuthorizedToModifyChannelByLegacyPermissionsSpy.calls.count() ).toBe(0); }); - it("return false if channelPermission.canModerateChannel is false and user is undefined", () => { + it("return false if channelPermission.canModerateChannel is true and channelPermission.canUpdateProperties is false", () => { hasOrgAdminUpdateRightsSpy.and.callFake(() => false); - canModerateChannelSpy.and.callFake(() => false); + canModerateChannelSpy.and.callFake(() => true); + canUpdatePropertiesSpy.and.callFake(() => false); const user = undefined as unknown as IUser; const channel = { @@ -116,6 +132,10 @@ describe("canEditChannel", () => { const [arg1] = canModerateChannelSpy.calls.allArgs()[0]; // args for 1st call expect(arg1).toEqual({}); + expect(canUpdatePropertiesSpy.calls.count()).toBe(1); + const [arg2] = canUpdatePropertiesSpy.calls.allArgs()[0]; // args for 1st call + expect(arg2).toEqual({}); + expect( isAuthorizedToModifyChannelByLegacyPermissionsSpy.calls.count() ).toBe(0); @@ -134,6 +154,7 @@ describe("canEditChannel", () => { expect(canEditChannel(channel, user)).toBe(true); expect(canModerateChannelSpy.calls.count()).toBe(0); + expect(canUpdatePropertiesSpy.calls.count()).toBe(0); expect( isAuthorizedToModifyChannelByLegacyPermissionsSpy.calls.count() @@ -155,6 +176,7 @@ describe("canEditChannel", () => { expect(canEditChannel(channel, user)).toBe(false); expect(canModerateChannelSpy.calls.count()).toBe(0); + expect(canUpdatePropertiesSpy.calls.count()).toBe(0); expect( isAuthorizedToModifyChannelByLegacyPermissionsSpy.calls.count()