Skip to content

Commit

Permalink
feat: post-mod and post-delete utils for ACL (#1119)
Browse files Browse the repository at this point in the history
  • Loading branch information
rgwozdz authored Jul 14, 2023
1 parent 2a7c3de commit 0a6292f
Show file tree
Hide file tree
Showing 14 changed files with 372 additions and 420 deletions.
249 changes: 120 additions & 129 deletions packages/discussions/src/utils/channel-permission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@ export class ChannelPermission {

private isChannelAclEmpty: boolean;
private permissionsByCategory: PermissionsByAclCategoryMap;
private channelCreator: string;

constructor(channelAcl: IChannelAclPermission[]) {
constructor(channelAcl: IChannelAclPermission[], creator: string) {
this.isChannelAclEmpty = channelAcl.length === 0;
this.permissionsByCategory = {};
this.channelCreator = creator;

channelAcl.forEach((permission) => {
const { category } = permission;
Expand All @@ -42,7 +44,7 @@ export class ChannelPermission {
}

canPostToChannel(user: IDiscussionsUser) {
if (this.aclAllowsAnyUserToPost()) {
if (this.canAnyUserWrite()) {
return true;
}

Expand All @@ -51,17 +53,13 @@ export class ChannelPermission {
}

return (
this.aclAllowsAnyAuthenticatedUserToPost() ||
this.aclAllowsThisUserToPost(user) ||
this.aclAllowsThisUserToPostByGroups(user) ||
this.aclAllowsThisUserToPostByOrg(user)
this.canAnyAuthenticatedUserWrite() ||
this.isUserAWriteUser(user) ||
this.isUserPartOfWriteGroup(user) ||
this.isUserPartOfWriteOrg(user)
);
}

canModifyPostStatus(user: IDiscussionsUser, channelCreator: string) {
return this.canModifyChannel(user, channelCreator);
}

canCreateChannel(user: IDiscussionsUser) {
if (this.isUserUnAuthenticated(user) || this.isChannelAclEmpty) {
return false;
Expand All @@ -76,87 +74,109 @@ export class ChannelPermission {
);
}

canModifyChannel(user: IDiscussionsUser, channelCreator: string) {
canModerateChannel(user: IDiscussionsUser) {
if (this.isUserUnAuthenticated(user)) {
return false;
}

if (user.username === channelCreator) {
return true;
}

return (
this.aclAllowsThisUserToModifyChannel(user) ||
this.aclAllowsThisUserToModifyChannelByGroups(user) ||
this.aclAllowsThisUserToModifyChannelByOrg(user)
user.username === this.channelCreator ||
this.isUserAModeratorUser(user) ||
this.isUserPartOfModeratorGroup(user) ||
this.isUserPartOfModeratorOrg(user)
);
}

private isAuthorizedToPost(role?: Role) {
return this.ALLOWED_ROLES_FOR_POSTING.includes(role);
/**
* canCreateChannelHelpers
*/
private userCanAddAnonymousToAcl(user: IDiscussionsUser) {
if (!this.permissionsByCategory[AclCategory.ANONYMOUS_USER]) {
return true;
}
return isOrgAdmin(user);
}

private isAuthorizedToModerate(role: Role) {
return this.ALLOWED_ROLES_FOR_MODERATION.includes(role);
private userCanAddUnauthenticatedToAcl(user: IDiscussionsUser) {
if (!this.permissionsByCategory[AclCategory.AUTHENTICATED_USER]) {
return true;
}
return isOrgAdmin(user);
}

private isUserUnAuthenticated(user: IDiscussionsUser) {
return user.username === null || user.username === undefined;
}
private userCanAddAllGroupsToAcl(user: IDiscussionsUser) {
const groupPermissions = this.permissionsByCategory[AclCategory.GROUP];
const userGroupsById = this.mapUserGroupsById(user.groups);

private mapUserGroupsById(groups: IGroup[]) {
return groups.reduce((accum, userGroup) => {
accum[userGroup.id] = userGroup;
return accum;
}, {} as Record<string, IGroup>);
if (!groupPermissions) {
return true;
}

return groupPermissions.every((permission) => {
const userGroup = userGroupsById[permission.key];

return (
userGroup &&
this.isMemberTypeAuthorized(userGroup) &&
this.isGroupDiscussable(userGroup)
);
});
}

private isMemberTypeAuthorized(userGroup: IGroup) {
const {
userMembership: { memberType },
} = userGroup;
return this.ALLOWED_GROUP_MEMBER_TYPES.includes(memberType);
private userCanAddAllOrgsToAcl(user: IDiscussionsUser) {
const orgPermissions = this.permissionsByCategory[AclCategory.ORG];

if (!orgPermissions) {
return true;
}

return (
isOrgAdmin(user) &&
this.isEveryPermissionForUserOrg(user.orgId, orgPermissions)
);
}

private isMemberTypeAdmin(userGroup: IGroup) {
const {
userMembership: { memberType },
} = userGroup;
return this.ADMIN_GROUP_MEMBER_TYPES.includes(memberType);
private isEveryPermissionForUserOrg(
userOrgId: string,
orgPermissions: IChannelAclPermissionDefinition[]
) {
return orgPermissions.every((permission) => {
const { key: orgId } = permission;
return userOrgId === orgId;
});
}

private isGroupDiscussable(userGroup: IGroup) {
const { typeKeywords = [] } = userGroup;
return !typeKeywords.includes(CANNOT_DISCUSS);
// for now user permissions are disabled on channel create
// since users are not notified and cannot opt out
private userCanAddUsersToAcl(user: IDiscussionsUser) {
const userPermissions = this.permissionsByCategory[AclCategory.USER];
return !userPermissions;
}

/**
* canPostToChannel helpers
*/
private aclAllowsAnyUserToPost() {
private canAnyUserWrite() {
const role =
this.permissionsByCategory[AclCategory.ANONYMOUS_USER]?.[0].role;
return this.isAuthorizedToPost(role);
return this.isAuthorizedToWritePost(role);
}

private aclAllowsAnyAuthenticatedUserToPost() {
private canAnyAuthenticatedUserWrite() {
const role =
this.permissionsByCategory[AclCategory.AUTHENTICATED_USER]?.[0].role;
return this.isAuthorizedToPost(role);
return this.isAuthorizedToWritePost(role);
}

private aclAllowsThisUserToPost(user: IDiscussionsUser) {
private isUserAWriteUser(user: IDiscussionsUser) {
const userPermissions = this.permissionsByCategory[AclCategory.USER] ?? [];
const username = user.username;

return userPermissions.some((permission) => {
const { role, key } = permission;

return key === username && this.isAuthorizedToPost(role);
return key === username && this.isAuthorizedToWritePost(role);
});
}

private aclAllowsThisUserToPostByGroups(user: IDiscussionsUser) {
private isUserPartOfWriteGroup(user: IDiscussionsUser) {
const groupPermissions =
this.permissionsByCategory[AclCategory.GROUP] ?? [];
const userGroupsById = this.mapUserGroupsById(user.groups);
Expand All @@ -177,19 +197,20 @@ export class ChannelPermission {
private canAnyGroupMemberPost(permission: IChannelAclPermission) {
const { subCategory, role } = permission;
return (
subCategory === AclSubCategory.MEMBER && this.isAuthorizedToPost(role)
subCategory === AclSubCategory.MEMBER &&
this.isAuthorizedToWritePost(role)
);
}

private canAdminsPost(permission: IChannelAclPermission) {
const { subCategory, role } = permission;

return (
subCategory === AclSubCategory.ADMIN && this.isAuthorizedToPost(role)
subCategory === AclSubCategory.ADMIN && this.isAuthorizedToWritePost(role)
);
}

private aclAllowsThisUserToPostByOrg(user: IDiscussionsUser) {
private isUserPartOfWriteOrg(user: IDiscussionsUser) {
const orgPermissions = this.permissionsByCategory[AclCategory.ORG] ?? [];
const { orgId: userOrgId } = user;

Expand All @@ -207,14 +228,50 @@ export class ChannelPermission {
private canAnyOrgMemberPost(permission: IChannelAclPermission) {
const { subCategory, role } = permission;
return (
subCategory === AclSubCategory.MEMBER && this.isAuthorizedToPost(role)
subCategory === AclSubCategory.MEMBER &&
this.isAuthorizedToWritePost(role)
);
}

/**
* canModifyChannel helpers
*/
private aclAllowsThisUserToModifyChannel(user: IDiscussionsUser) {
private isAuthorizedToWritePost(role?: Role) {
return this.ALLOWED_ROLES_FOR_POSTING.includes(role);
}

private isUserUnAuthenticated(user: IDiscussionsUser) {
return user.username === null || user.username === undefined;
}

private mapUserGroupsById(groups: IGroup[]) {
return groups.reduce((accum, userGroup) => {
accum[userGroup.id] = userGroup;
return accum;
}, {} as Record<string, IGroup>);
}

private isMemberTypeAuthorized(userGroup: IGroup) {
const {
userMembership: { memberType },
} = userGroup;
return this.ALLOWED_GROUP_MEMBER_TYPES.includes(memberType);
}

private isMemberTypeAdmin(userGroup: IGroup) {
const {
userMembership: { memberType },
} = userGroup;
return this.ADMIN_GROUP_MEMBER_TYPES.includes(memberType);
}

private isGroupDiscussable(userGroup: IGroup) {
const { typeKeywords = [] } = userGroup;
return !typeKeywords.includes(CANNOT_DISCUSS);
}

private isAuthorizedToModerate(role: Role) {
return this.ALLOWED_ROLES_FOR_MODERATION.includes(role);
}

private isUserAModeratorUser(user: IDiscussionsUser) {
const userPermissions = this.permissionsByCategory[AclCategory.USER] ?? [];
const username = user.username;

Expand All @@ -225,7 +282,7 @@ export class ChannelPermission {
});
}

private aclAllowsThisUserToModifyChannelByGroups(user: IDiscussionsUser) {
private isUserPartOfModeratorGroup(user: IDiscussionsUser) {
const groupPermissions =
this.permissionsByCategory[AclCategory.GROUP] ?? [];
const userGroupsById = this.mapUserGroupsById(user.groups);
Expand Down Expand Up @@ -258,7 +315,7 @@ export class ChannelPermission {
);
}

private aclAllowsThisUserToModifyChannelByOrg(user: IDiscussionsUser) {
private isUserPartOfModeratorOrg(user: IDiscussionsUser) {
const orgPermissions = this.permissionsByCategory[AclCategory.ORG] ?? [];
const { orgId: userOrgId } = user;

Expand All @@ -279,70 +336,4 @@ export class ChannelPermission {
subCategory === AclSubCategory.MEMBER && this.isAuthorizedToModerate(role)
);
}

/**
* canCreateChannelHelpers
*/
private userCanAddAnonymousToAcl(user: IDiscussionsUser) {
if (!this.permissionsByCategory[AclCategory.ANONYMOUS_USER]) {
return true;
}
return isOrgAdmin(user);
}

private userCanAddUnauthenticatedToAcl(user: IDiscussionsUser) {
if (!this.permissionsByCategory[AclCategory.AUTHENTICATED_USER]) {
return true;
}
return isOrgAdmin(user);
}

private userCanAddAllGroupsToAcl(user: IDiscussionsUser) {
const groupPermissions = this.permissionsByCategory[AclCategory.GROUP];
const userGroupsById = this.mapUserGroupsById(user.groups);

if (!groupPermissions) {
return true;
}

return groupPermissions.every((permission) => {
const userGroup = userGroupsById[permission.key];

return (
userGroup &&
this.isMemberTypeAuthorized(userGroup) &&
this.isGroupDiscussable(userGroup)
);
});
}

private userCanAddAllOrgsToAcl(user: IDiscussionsUser) {
const orgPermissions = this.permissionsByCategory[AclCategory.ORG];

if (!orgPermissions) {
return true;
}

return (
isOrgAdmin(user) &&
this.isEveryPermissionForUserOrg(user.orgId, orgPermissions)
);
}

private isEveryPermissionForUserOrg(
userOrgId: string,
orgPermissions: IChannelAclPermissionDefinition[]
) {
return orgPermissions.every((permission) => {
const { key: orgId } = permission;
return userOrgId === orgId;
});
}

// for now user permissions are disabled on channel create
// since users are not notified and cannot opt out
private userCanAddUsersToAcl(user: IDiscussionsUser) {
const userPermissions = this.permissionsByCategory[AclCategory.USER];
return !userPermissions;
}
}
4 changes: 2 additions & 2 deletions packages/discussions/src/utils/channels/can-create-channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ export function canCreateChannel(
channel: IChannel,
user: IUser | IDiscussionsUser = {}
): boolean {
const { channelAcl, access, groups, orgs } = channel;
const { channelAcl, access, groups, orgs, creator } = channel;

if (channelAcl) {
const channelPermission = new ChannelPermission(channelAcl);
const channelPermission = new ChannelPermission(channelAcl, creator);
return channelPermission.canCreateChannel(user as IDiscussionsUser);
}

Expand Down
9 changes: 3 additions & 6 deletions packages/discussions/src/utils/channels/can-modify-channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,11 @@ export function canModifyChannel(
channel: IChannel,
user: IUser | IDiscussionsUser = {}
): boolean {
const { channelAcl } = channel;
const { channelAcl, creator } = channel;

if (channelAcl) {
const channelPermission = new ChannelPermission(channelAcl);
return channelPermission.canModifyChannel(
user as IDiscussionsUser,
channel.creator
);
const channelPermission = new ChannelPermission(channelAcl, creator);
return channelPermission.canModerateChannel(user as IDiscussionsUser);
}

return isAuthorizedToModifyChannelByLegacyPermissions(user, channel);
Expand Down
Loading

0 comments on commit 0a6292f

Please sign in to comment.