Skip to content

Commit

Permalink
feat(hub-discussions): add post util functions canCreatePost and canC…
Browse files Browse the repository at this point in the history
…reateReply

affects: @esri/hub-discussions
  • Loading branch information
velveetachef committed Dec 4, 2024
1 parent f0bba80 commit 7df0ada
Show file tree
Hide file tree
Showing 9 changed files with 1,074 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type ILegacyChannelPermissions = Pick<

/**
* Utility to determine if User has privileges to create a post in a channel
* @deprecated use `canCreatePost` or 'canCreateReply` instead
* @param channel
* @param user
* @returns {boolean}
Expand Down
98 changes: 98 additions & 0 deletions packages/discussions/src/utils/posts/can-create-post.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { IGroup, IUser } from "@esri/arcgis-rest-types";
import { IChannel, IDiscussionsUser, SharingAccess } from "../../types";
import { ChannelPermission } from "../channel-permission";
import { CANNOT_DISCUSS } from "../constants";
import { hasOrgAdminUpdateRights } from "../portal-privilege";

const ALLOWED_GROUP_ROLES = Object.freeze(["owner", "admin", "member"]);

type ILegacyChannelPermissions = Pick<
IChannel,
"groups" | "orgs" | "access" | "allowAnonymous"
>;

/**
* Utility to determine if User has privileges to create a post in a channel
* @param channel
* @param user
* @returns {boolean}
*/
export function canCreatePost(
channel: IChannel,
user: IUser | IDiscussionsUser = {}
): boolean {
const { access, groups, orgs, allowAnonymous, allowPost } = channel;

if (hasOrgAdminUpdateRights(user, channel.orgId)) {
return true;
}

if (!allowPost) {
return false;
}

if (channel.channelAcl) {
const channelPermission = new ChannelPermission(channel);
return channelPermission.canPostToChannel(user as IDiscussionsUser);
}

// Once channelAcl usage is enforced, we will remove authorization by legacy permissions
return isAuthorizedToPostByLegacyPermissions(user, {
access,
groups,
orgs,
allowAnonymous,
});
}

function isAuthorizedToPostByLegacyPermissions(
user: IUser | IDiscussionsUser,
channelParams: ILegacyChannelPermissions
): boolean {
const { username, groups: userGroups, orgId: userOrgId } = user;
const { allowAnonymous, access, groups, orgs } = channelParams;

// order is important here
if (allowAnonymous === true) {
return true;
}

if (!username) {
return false;
}

if (access === SharingAccess.PUBLIC) {
return true;
}

if (access === SharingAccess.PRIVATE) {
return isAuthorizedToPostByLegacyGroup(groups, userGroups);
}

if (access === SharingAccess.ORG) {
return orgs.includes(userOrgId);
}

return false;
}

function isAuthorizedToPostByLegacyGroup(
channelGroups: string[] = [],
userGroups: IGroup[] = []
) {
return channelGroups.some((channelGroupId: string) => {
return userGroups.some((group: IGroup) => {
const {
id: userGroupId,
userMembership: { memberType: userMemberType },
typeKeywords,
} = group;

return (
channelGroupId === userGroupId &&
ALLOWED_GROUP_ROLES.includes(userMemberType) &&
!typeKeywords.includes(CANNOT_DISCUSS)
);
});
});
}
98 changes: 98 additions & 0 deletions packages/discussions/src/utils/posts/can-create-reply.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { IGroup, IUser } from "@esri/arcgis-rest-types";
import { IChannel, IDiscussionsUser, SharingAccess } from "../../types";
import { ChannelPermission } from "../channel-permission";
import { CANNOT_DISCUSS } from "../constants";
import { hasOrgAdminUpdateRights } from "../portal-privilege";

const ALLOWED_GROUP_ROLES = Object.freeze(["owner", "admin", "member"]);

type ILegacyChannelPermissions = Pick<
IChannel,
"groups" | "orgs" | "access" | "allowAnonymous"
>;

/**
* Utility to determine if User has privileges to create a post in a channel
* @param channel
* @param user
* @returns {boolean}
*/
export function canCreateReply(
channel: IChannel,
user: IUser | IDiscussionsUser = {}
): boolean {
const { access, groups, orgs, allowAnonymous, allowReply } = channel;

if (hasOrgAdminUpdateRights(user, channel.orgId)) {
return true;
}

if (!allowReply) {
return false;
}

if (channel.channelAcl) {
const channelPermission = new ChannelPermission(channel);
return channelPermission.canPostToChannel(user as IDiscussionsUser);
}

// Once channelAcl usage is enforced, we will remove authorization by legacy permissions
return isAuthorizedToPostByLegacyPermissions(user, {
access,
groups,
orgs,
allowAnonymous,
});
}

function isAuthorizedToPostByLegacyPermissions(
user: IUser | IDiscussionsUser,
channelParams: ILegacyChannelPermissions
): boolean {
const { username, groups: userGroups, orgId: userOrgId } = user;
const { allowAnonymous, access, groups, orgs } = channelParams;

// order is important here
if (allowAnonymous === true) {
return true;
}

if (!username) {
return false;
}

if (access === SharingAccess.PUBLIC) {
return true;
}

if (access === SharingAccess.PRIVATE) {
return isAuthorizedToPostByLegacyGroup(groups, userGroups);
}

if (access === SharingAccess.ORG) {
return orgs.includes(userOrgId);
}

return false;
}

function isAuthorizedToPostByLegacyGroup(
channelGroups: string[] = [],
userGroups: IGroup[] = []
) {
return channelGroups.some((channelGroupId: string) => {
return userGroups.some((group: IGroup) => {
const {
id: userGroupId,
userMembership: { memberType: userMemberType },
typeKeywords,
} = group;

return (
channelGroupId === userGroupId &&
ALLOWED_GROUP_ROLES.includes(userMemberType) &&
!typeKeywords.includes(CANNOT_DISCUSS)
);
});
});
}
8 changes: 7 additions & 1 deletion packages/discussions/src/utils/posts/can-edit-post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,16 @@ export function canEditPost(
channel: IChannel
): boolean {
const { access, groups, orgs, allowAnonymous } = channel;

if (channel.channelAcl) {
const canReplyOrPost = post.parentId
? channel.allowReply
: channel.allowPost;
const channelPermission = new ChannelPermission(channel);
return (
isPostCreator(post, user) && channelPermission.canPostToChannel(user)
isPostCreator(post, user) &&
canReplyOrPost &&
channelPermission.canPostToChannel(user)
);
}

Expand Down
2 changes: 2 additions & 0 deletions packages/discussions/src/utils/posts/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export { canCreatePost } from "./can-create-post";
export { canCreateReply } from "./can-create-reply";
export { canDeletePost } from "./can-delete-post";
export { canModifyPostStatus, canEditPostStatus } from "./can-edit-post-status";
export { canModifyPost, canEditPost } from "./can-edit-post";
Expand Down
14 changes: 8 additions & 6 deletions packages/discussions/src/utils/reactions/can-create-reaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@ function channelAllowsReaction(
value: PostReaction
): boolean {
const { allowReaction, allowedReactions } = channel;
if (allowReaction) {
if (allowedReactions) {
return allowedReactions.includes(value);
}
return true;
if (!allowReaction) {
return false;
}

if (allowedReactions) {
return allowedReactions.includes(value);
}
return false;

return true;
}
Loading

0 comments on commit 7df0ada

Please sign in to comment.