From 32f5c96dd2c8c0c8672720b747290ed8d6f27e44 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Sun, 26 Nov 2023 13:18:49 +0700 Subject: [PATCH] Move custom role paywall to assignment step --- .../controllers/v1/membershipController.ts | 9 +++++- .../controllers/v2/organizationsController.ts | 19 ++++++++++-- .../src/controllers/v2/workspaceController.ts | 4 +-- backend/src/ee/services/EELicenseService.ts | 2 +- backend/src/ee/services/ProjectRoleService.ts | 2 +- backend/src/ee/services/RoleService.ts | 2 +- .../OrgMembersSection/OrgMembersSection.tsx | 6 ++-- .../OrgMembersSection/OrgMembersTable.tsx | 30 +++++++++++++------ .../OrgRoleModifySection.tsx | 24 ++------------- .../MachineIdentityTable.tsx | 10 +------ .../MemberListTab/MemberListTab.tsx | 18 ++++++++--- .../ProjectRoleModifySection.tsx | 24 ++------------- 12 files changed, 73 insertions(+), 77 deletions(-) diff --git a/backend/src/controllers/v1/membershipController.ts b/backend/src/controllers/v1/membershipController.ts index cb11c4b74..6db82a201 100644 --- a/backend/src/controllers/v1/membershipController.ts +++ b/backend/src/controllers/v1/membershipController.ts @@ -6,7 +6,7 @@ import { deleteMembership as deleteMember, findMembership } from "../../helpers/ import { sendMail } from "../../helpers/nodemailer"; import { ACCEPTED, ADMIN, CUSTOM, MEMBER, VIEWER } from "../../variables"; import { getSiteURL } from "../../config"; -import { EEAuditLogService } from "../../ee/services"; +import { EEAuditLogService, EELicenseService } from "../../ee/services"; import { validateRequest } from "../../helpers/validation"; import * as reqValidator from "../../validation/membership"; import { @@ -137,6 +137,13 @@ export const changeMembershipRole = async (req: Request, res: Response) => { workspace: membershipToChangeRole.workspace }); if (!wsRole) throw BadRequestError({ message: "Role not found" }); + + const plan = await EELicenseService.getPlan(wsRole.organization); + + if (!plan.rbac) return res.status(400).send({ + message: "Failed to assign custom role due to RBAC restriction. Upgrade plan to assign custom role to member." + }); + const membership = await Membership.findByIdAndUpdate(membershipId, { role: CUSTOM, customRole: wsRole diff --git a/backend/src/controllers/v2/organizationsController.ts b/backend/src/controllers/v2/organizationsController.ts index 0ff16d00e..684273e24 100644 --- a/backend/src/controllers/v2/organizationsController.ts +++ b/backend/src/controllers/v2/organizationsController.ts @@ -15,7 +15,7 @@ import { } from "../../helpers/organization"; import { addMembershipsOrg } from "../../helpers/membershipOrg"; import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors"; -import { ACCEPTED, ADMIN, CUSTOM } from "../../variables"; +import { ACCEPTED, ADMIN, CUSTOM, MEMBER } from "../../variables"; import * as reqValidator from "../../validation/organization"; import { validateRequest } from "../../helpers/validation"; import { @@ -23,6 +23,7 @@ import { OrgPermissionSubjects, getUserOrgPermissions } from "../../ee/services/RoleService"; +import { EELicenseService } from "../../ee/services"; import { ForbiddenError } from "@casl/ability"; /** @@ -152,10 +153,22 @@ export const updateOrganizationMembership = async (req: Request, res: Response) OrgPermissionSubjects.Member ); - const isCustomRole = !["admin", "member"].includes(role); + const isCustomRole = ![ADMIN, MEMBER].includes(role); if (isCustomRole) { - const orgRole = await Role.findOne({ slug: role, isOrgRole: true }); + const orgRole = await Role.findOne({ + slug: role, + isOrgRole: true, + organization: new Types.ObjectId(organizationId) + }); + if (!orgRole) throw BadRequestError({ message: "Role not found" }); + + const plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId)); + + if (!plan.rbac) return res.status(400).send({ + message: + "Failed to assign custom role due to RBAC restriction. Upgrade plan to assign custom role to member." + }); const membership = await MembershipOrg.findByIdAndUpdate(membershipId, { role: CUSTOM, diff --git a/backend/src/controllers/v2/workspaceController.ts b/backend/src/controllers/v2/workspaceController.ts index 55f0b1617..55e5ea7d1 100644 --- a/backend/src/controllers/v2/workspaceController.ts +++ b/backend/src/controllers/v2/workspaceController.ts @@ -30,7 +30,7 @@ import { } from "../../ee/services/ProjectRoleService"; import { ForbiddenError } from "@casl/ability"; import { BadRequestError, ForbiddenRequestError, ResourceNotFoundError } from "../../utils/errors"; -import { ADMIN, MEMBER, VIEWER } from "../../variables"; +import { ADMIN, CUSTOM, MEMBER, VIEWER } from "../../variables"; interface V2PushSecret { type: string; // personal or shared @@ -571,7 +571,7 @@ export const addMachineToWorkspace = async (req: Request, res: Response) => { machineMembership = await new MachineMembership({ machineIdentity: machineIdentity._id, workspace: new Types.ObjectId(workspaceId), - role, + role: customRole ? CUSTOM : role, customRole }).save(); diff --git a/backend/src/ee/services/EELicenseService.ts b/backend/src/ee/services/EELicenseService.ts index 7052263c5..6baea04dc 100644 --- a/backend/src/ee/services/EELicenseService.ts +++ b/backend/src/ee/services/EELicenseService.ts @@ -66,7 +66,7 @@ class EELicenseService { secretVersioning: true, pitRecovery: false, ipAllowlisting: false, - rbac: true, + rbac: false, customRateLimits: false, customAlerts: false, auditLogs: false, diff --git a/backend/src/ee/services/ProjectRoleService.ts b/backend/src/ee/services/ProjectRoleService.ts index f20114b87..bd9f68253 100644 --- a/backend/src/ee/services/ProjectRoleService.ts +++ b/backend/src/ee/services/ProjectRoleService.ts @@ -385,7 +385,7 @@ export const getRolePermissions = async (role: string, workspaceId: string) => { * @param ability * @returns */ - const extractPermissions = (ability: MongoAbility | ProjectPermissionSet) => { + const extractPermissions = (ability: any) => { return ability.A.map((permission: any) => `${permission.action}_${permission.subject}`); } diff --git a/backend/src/ee/services/RoleService.ts b/backend/src/ee/services/RoleService.ts index f4d65cb75..a1e191330 100644 --- a/backend/src/ee/services/RoleService.ts +++ b/backend/src/ee/services/RoleService.ts @@ -178,7 +178,7 @@ export const getOrgRolePermissions = async (role: string, orgId: string) => { * @param ability * @returns */ -const extractPermissions = (ability: MongoAbility | OrgPermissionSet) => { +const extractPermissions = (ability: any) => { return ability.A.map((permission: any) => `${permission.action}_${permission.subject}`); } diff --git a/frontend/src/views/Org/MembersPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersSection.tsx b/frontend/src/views/Org/MembersPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersSection.tsx index ecfe16749..d96e0d7c4 100644 --- a/frontend/src/views/Org/MembersPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersSection.tsx +++ b/frontend/src/views/Org/MembersPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersSection.tsx @@ -57,7 +57,9 @@ export const OrgMembersSection = () => { } if (isMoreUsersNotAllowed) { - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + description: "You can add more members if you upgrade your Infisical plan." + }); } else { handlePopUpOpen("addMember"); } @@ -134,7 +136,7 @@ export const OrgMembersSection = () => { handlePopUpToggle("upgradePlan", isOpen)} - text="You can add more members if you upgrade your Infisical plan." + text={(popUp.upgradePlan?.data as { description: string })?.description} /> , + popUpName: keyof UsePopUpState<["removeMember", "upgradePlan"]>, data?: { orgMembershipId?: string; email?: string; + description?: string; } ) => void; setCompleteInviteLink: (link: string) => void; @@ -55,6 +56,7 @@ export const OrgMembersTable = ({ setCompleteInviteLink }: Props) => { const { createNotification } = useNotificationContext(); + const { subscription } = useSubscription(); const { currentOrg } = useOrganization(); const { user } = useUser(); const userId = user?._id || ""; @@ -63,7 +65,7 @@ export const OrgMembersTable = ({ const { data: roles, isLoading: isRolesLoading } = useGetRoles({ orgId }); - + const [searchMemberFilter, setSearchMemberFilter] = useState(""); const { data: serverDetails } = useFetchServerStatus(); @@ -76,10 +78,21 @@ export const OrgMembersTable = ({ if (!currentOrg?._id) return; try { + // TODO: replace hardcoding default role + const isCustomRole = !["admin", "member"].includes(role); + + if (isCustomRole && subscription && !subscription?.rbac) { + handlePopUpOpen("upgradePlan", { + description: "You can assign custom roles to members if you upgrade your Infisical plan." + }); + return; + } + await updateUserOrgRole({ - organizationId: currentOrg?._id, - membershipId, role - }); + organizationId: currentOrg?._id, + membershipId, role + }); + createNotification({ text: "Successfully updated user role", type: "success" @@ -169,7 +182,6 @@ export const OrgMembersTable = ({ ({ user: u, inviteEmail, role, customRole, _id: orgMembershipId, status }) => { const name = u ? `${u.firstName} ${u.lastName}` : "-"; const email = u?.email || inviteEmail; - return ( {name} @@ -183,7 +195,7 @@ export const OrgMembersTable = ({ <> {status === "accepted" && ( ; export const MemberListTab = () => { const { createNotification } = useNotificationContext(); + const { subscription } = useSubscription(); const { t } = useTranslation(); const { currentOrg } = useOrganization(); @@ -170,6 +171,15 @@ export const MemberListTab = () => { if (!currentOrg?._id) return; try { + const isCustomRole = !["admin", "member", "viewer"].includes(role); + + if (isCustomRole && subscription && !subscription?.rbac) { + handlePopUpOpen("upgradePlan", { + description: "You can assign custom roles to members if you upgrade your Infisical plan." + }); + return; + } + await updateUserWorkspaceRole({ membershipId, role }); createNotification({ text: "Successfully updated user role", @@ -302,7 +312,7 @@ export const MemberListTab = () => { {(isAllowed) => ( <>