diff --git a/backend/src/controllers/v1/membershipOrgController.ts b/backend/src/controllers/v1/membershipOrgController.ts index eeba96783..f212892ee 100644 --- a/backend/src/controllers/v1/membershipOrgController.ts +++ b/backend/src/controllers/v1/membershipOrgController.ts @@ -21,7 +21,7 @@ import { validateRequest } from "../../helpers/validation"; import { OrgPermissionActions, OrgPermissionSubjects, - getUserOrgPermissions + getAuthDataOrgPermissions } from "../../ee/services/RoleService"; import { ForbiddenError } from "@casl/ability"; @@ -44,11 +44,12 @@ export const deleteMembershipOrg = async (req: Request, _res: Response) => { if (!membershipOrgToDelete) { throw new Error("Failed to delete organization membership that doesn't exist"); } + + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: membershipOrgToDelete.organization + }); - const { permission, membership: membershipOrg } = await getUserOrgPermissions( - req.user._id, - membershipOrgToDelete.organization.toString() - ); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Delete, OrgPermissionSubjects.Member @@ -60,7 +61,7 @@ export const deleteMembershipOrg = async (req: Request, _res: Response) => { }); await updateSubscriptionOrgQuantity({ - organizationId: membershipOrg.organization.toString() + organizationId: membershipOrgToDelete.organization.toString() }); return membershipOrgToDelete; @@ -96,7 +97,11 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => { body: { inviteeEmail, organizationId } } = await validateRequest(reqValidator.InviteUserToOrgv1, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); + ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Create, OrgPermissionSubjects.Member diff --git a/backend/src/controllers/v1/organizationController.ts b/backend/src/controllers/v1/organizationController.ts index 2d216b6c5..676cb5572 100644 --- a/backend/src/controllers/v1/organizationController.ts +++ b/backend/src/controllers/v1/organizationController.ts @@ -1,4 +1,5 @@ import { Request, Response } from "express"; +import { Types } from "mongoose"; import { IncidentContactOrg, Membership, @@ -14,7 +15,7 @@ import { ACCEPTED } from "../../variables"; import { OrgPermissionActions, OrgPermissionSubjects, - getUserOrgPermissions + getAuthDataOrgPermissions } from "../../ee/services/RoleService"; import { OrganizationNotFoundError } from "../../utils/errors"; import { ForbiddenError } from "@casl/ability"; @@ -44,7 +45,10 @@ export const getOrganization = async (req: Request, res: Response) => { } = await validateRequest(reqValidator.GetOrgv1, req); // ensure user has membership - await getUserOrgPermissions(req.user._id, organizationId); + await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }) const organization = await Organization.findById(organizationId); if (!organization) { @@ -68,8 +72,12 @@ export const getOrganizationMembers = async (req: Request, res: Response) => { const { params: { organizationId } } = await validateRequest(reqValidator.GetOrgMembersv1, req); - - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); + ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Read, OrgPermissionSubjects.Member @@ -95,7 +103,10 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) => params: { organizationId } } = await validateRequest(reqValidator.GetOrgWorkspacesv1, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }) ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Read, OrgPermissionSubjects.Workspace @@ -137,7 +148,10 @@ export const changeOrganizationName = async (req: Request, res: Response) => { body: { name } } = await validateRequest(reqValidator.ChangeOrgNamev1, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Edit, OrgPermissionSubjects.Settings @@ -172,7 +186,10 @@ export const getOrganizationIncidentContacts = async (req: Request, res: Respons params: { organizationId } } = await validateRequest(reqValidator.GetOrgIncidentContactv1, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Read, OrgPermissionSubjects.IncidentAccount @@ -199,7 +216,10 @@ export const addOrganizationIncidentContact = async (req: Request, res: Response body: { email } } = await validateRequest(reqValidator.CreateOrgIncideContact, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Create, OrgPermissionSubjects.IncidentAccount @@ -228,7 +248,10 @@ export const deleteOrganizationIncidentContact = async (req: Request, res: Respo body: { email } } = await validateRequest(reqValidator.DelOrgIncideContact, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Delete, OrgPermissionSubjects.IncidentAccount @@ -257,7 +280,10 @@ export const createOrganizationPortalSession = async (req: Request, res: Respons params: { organizationId } } = await validateRequest(reqValidator.GetOrgPlanBillingInfov1, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Edit, OrgPermissionSubjects.Billing @@ -321,7 +347,10 @@ export const getOrganizationMembersAndTheirWorkspaces = async (req: Request, res params: { organizationId } } = await validateRequest(reqValidator.GetOrgMembersv1, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Read, OrgPermissionSubjects.Member diff --git a/backend/src/controllers/v1/secretScanningController.ts b/backend/src/controllers/v1/secretScanningController.ts index 581a168b1..292ce94b3 100644 --- a/backend/src/controllers/v1/secretScanningController.ts +++ b/backend/src/controllers/v1/secretScanningController.ts @@ -21,7 +21,7 @@ import * as reqValidator from "../../validation/secretScanning"; import { OrgPermissionActions, OrgPermissionSubjects, - getUserOrgPermissions + getAuthDataOrgPermissions } from "../../ee/services/RoleService"; import { ForbiddenError } from "@casl/ability"; @@ -37,8 +37,11 @@ export const createInstallationSession = async (req: Request, res: Response) => message: "Failed to find organization" }); } - - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Create, OrgPermissionSubjects.SecretScanning @@ -70,11 +73,12 @@ export const linkInstallationToOrganization = async (req: Request, res: Response if (!installationSession) { throw UnauthorizedRequestError(); } + + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: installationSession.organization + }); - const { permission } = await getUserOrgPermissions( - req.user._id, - installationSession.organization.toString() - ); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Edit, OrgPermissionSubjects.SecretScanning @@ -142,7 +146,10 @@ export const getRisksForOrganization = async (req: Request, res: Response) => { params: { organizationId } } = await validateRequest(reqValidator.GetOrgRisksv1, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning @@ -162,7 +169,10 @@ export const updateRisksStatus = async (req: Request, res: Response) => { body: { status } } = await validateRequest(reqValidator.UpdateRiskStatusv1, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Edit, OrgPermissionSubjects.SecretScanning diff --git a/backend/src/controllers/v1/workspaceController.ts b/backend/src/controllers/v1/workspaceController.ts index 9d3ae5b80..2d6e9776d 100644 --- a/backend/src/controllers/v1/workspaceController.ts +++ b/backend/src/controllers/v1/workspaceController.ts @@ -17,7 +17,7 @@ import { OrganizationNotFoundError } from "../../utils/errors"; import { OrgPermissionActions, OrgPermissionSubjects, - getUserOrgPermissions + getAuthDataOrgPermissions } from "../../ee/services/RoleService"; import { ForbiddenError } from "@casl/ability"; import { validateRequest } from "../../helpers/validation"; @@ -152,7 +152,10 @@ export const createWorkspace = async (req: Request, res: Response) => { }); } - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Create, OrgPermissionSubjects.Workspace diff --git a/backend/src/controllers/v2/organizationsController.ts b/backend/src/controllers/v2/organizationsController.ts index 5db8b6eb1..c06934520 100644 --- a/backend/src/controllers/v2/organizationsController.ts +++ b/backend/src/controllers/v2/organizationsController.ts @@ -14,14 +14,14 @@ import { updateSubscriptionOrgQuantity } from "../../helpers/organization"; import { addMembershipsOrg } from "../../helpers/membershipOrg"; -import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors"; +import { BadRequestError, ResourceNotFoundError, UnauthorizedRequestError } from "../../utils/errors"; import { ACCEPTED, ADMIN, CUSTOM, MEMBER, NO_ACCESS } from "../../variables"; import * as reqValidator from "../../validation/organization"; import { validateRequest } from "../../helpers/validation"; import { OrgPermissionActions, OrgPermissionSubjects, - getUserOrgPermissions + getAuthDataOrgPermissions } from "../../ee/services/RoleService"; import { EELicenseService } from "../../ee/services"; import { ForbiddenError } from "@casl/ability"; @@ -69,7 +69,10 @@ export const getOrganizationMemberships = async (req: Request, res: Response) => params: { organizationId } } = await validateRequest(reqValidator.GetOrgMembersv2, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Read, OrgPermissionSubjects.Member @@ -147,7 +150,11 @@ export const updateOrganizationMembership = async (req: Request, res: Response) params: { organizationId, membershipId }, body: { role } } = await validateRequest(reqValidator.UpdateOrgMemberv2, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Edit, OrgPermissionSubjects.Member @@ -245,7 +252,18 @@ export const deleteOrganizationMembership = async (req: Request, res: Response) const { params: { organizationId, membershipId } } = await validateRequest(reqValidator.DeleteOrgMemberv2, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + + const membershipOrg = await MembershipOrg.findOne({ + _id: new Types.ObjectId(membershipId), + organization: new Types.ObjectId(organizationId) + }); + + if (!membershipOrg) throw ResourceNotFoundError(); + + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: membershipOrg.organization + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Delete, OrgPermissionSubjects.Member @@ -309,7 +327,11 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) => params: { organizationId } } = await validateRequest(reqValidator.GetOrgWorkspacesv2, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); + ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Read, OrgPermissionSubjects.Workspace @@ -407,8 +429,10 @@ export const getOrganizationMachineMemberships = async (req: Request, res: Respo params: { organizationId } } = await validateRequest(reqValidator.GetOrgServiceMembersV2, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); - + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Read, OrgPermissionSubjects.MachineIdentity diff --git a/backend/src/ee/controllers/v1/organizationsController.ts b/backend/src/ee/controllers/v1/organizationsController.ts index f64603e3b..2f0d5ec39 100644 --- a/backend/src/ee/controllers/v1/organizationsController.ts +++ b/backend/src/ee/controllers/v1/organizationsController.ts @@ -8,7 +8,7 @@ import * as reqValidator from "../../../validation/organization"; import { OrgPermissionActions, OrgPermissionSubjects, - getUserOrgPermissions + getAuthDataOrgPermissions, } from "../../services/RoleService"; import { ForbiddenError } from "@casl/ability"; import { Organization } from "../../../models"; @@ -20,7 +20,10 @@ export const getOrganizationPlansTable = async (req: Request, res: Response) => params: { organizationId } } = await validateRequest(reqValidator.GetOrgPlansTablev1, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Read, OrgPermissionSubjects.Billing @@ -42,7 +45,10 @@ export const getOrganizationPlan = async (req: Request, res: Response) => { params: { organizationId } } = await validateRequest(reqValidator.GetOrgPlanv1, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Read, OrgPermissionSubjects.Billing @@ -70,7 +76,10 @@ export const startOrganizationTrial = async (req: Request, res: Response) => { body: { success_url } } = await validateRequest(reqValidator.StartOrgTrailv1, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Create, OrgPermissionSubjects.Billing @@ -116,7 +125,10 @@ export const getOrganizationPlanBillingInfo = async (req: Request, res: Response params: { organizationId } } = await validateRequest(reqValidator.GetOrgPlanBillingInfov1, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Read, OrgPermissionSubjects.Billing @@ -149,7 +161,10 @@ export const getOrganizationPlanTable = async (req: Request, res: Response) => { params: { organizationId } } = await validateRequest(reqValidator.GetOrgPlanTablev1, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Read, OrgPermissionSubjects.Billing @@ -176,7 +191,10 @@ export const getOrganizationBillingDetails = async (req: Request, res: Response) params: { organizationId } } = await validateRequest(reqValidator.GetOrgBillingDetailsv1, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Read, OrgPermissionSubjects.Billing @@ -204,7 +222,10 @@ export const updateOrganizationBillingDetails = async (req: Request, res: Respon body: { name, email } } = await validateRequest(reqValidator.UpdateOrgBillingDetailsv1, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Edit, OrgPermissionSubjects.Billing @@ -238,7 +259,10 @@ export const getOrganizationPmtMethods = async (req: Request, res: Response) => params: { organizationId } } = await validateRequest(reqValidator.GetOrgPmtMethodsv1, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Read, OrgPermissionSubjects.Billing @@ -271,7 +295,10 @@ export const addOrganizationPmtMethod = async (req: Request, res: Response) => { body: { success_url, cancel_url } } = await validateRequest(reqValidator.CreateOrgPmtMethodv1, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Create, OrgPermissionSubjects.Billing @@ -312,7 +339,10 @@ export const deleteOrganizationPmtMethod = async (req: Request, res: Response) = params: { organizationId, pmtMethodId } } = await validateRequest(reqValidator.DelOrgPmtMethodv1, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Delete, OrgPermissionSubjects.Billing @@ -342,7 +372,10 @@ export const getOrganizationTaxIds = async (req: Request, res: Response) => { params: { organizationId } } = await validateRequest(reqValidator.GetOrgTaxIdsv1, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Read, OrgPermissionSubjects.Billing @@ -375,7 +408,10 @@ export const addOrganizationTaxId = async (req: Request, res: Response) => { body: { type, value } } = await validateRequest(reqValidator.CreateOrgTaxId, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Create, OrgPermissionSubjects.Billing @@ -412,7 +448,10 @@ export const deleteOrganizationTaxId = async (req: Request, res: Response) => { params: { organizationId, taxId } } = await validateRequest(reqValidator.DelOrgTaxIdv1, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Delete, OrgPermissionSubjects.Billing @@ -445,7 +484,10 @@ export const getOrganizationInvoices = async (req: Request, res: Response) => { params: { organizationId } } = await validateRequest(reqValidator.GetOrgInvoicesv1, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Read, OrgPermissionSubjects.Billing @@ -480,7 +522,10 @@ export const getOrganizationLicenses = async (req: Request, res: Response) => { params: { organizationId } } = await validateRequest(reqValidator.GetOrgLicencesv1, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Read, OrgPermissionSubjects.Billing diff --git a/backend/src/ee/controllers/v1/roleController.ts b/backend/src/ee/controllers/v1/roleController.ts index 11ac29a63..a4b3035e6 100644 --- a/backend/src/ee/controllers/v1/roleController.ts +++ b/backend/src/ee/controllers/v1/roleController.ts @@ -22,6 +22,7 @@ import { OrgPermissionActions, OrgPermissionSubjects, adminPermissions, + getAuthDataOrgPermissions, getUserOrgPermissions, memberPermissions, noAccessPermissions @@ -38,7 +39,11 @@ export const createRole = async (req: Request, res: Response) => { const isOrgRole = !workspaceId; // if workspaceid is provided then its a workspace rule if (isOrgRole) { - const { permission } = await getUserOrgPermissions(req.user.id, orgId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(orgId) + }); + if (permission.cannot(OrgPermissionActions.Create, OrgPermissionSubjects.Role)) { throw BadRequestError({ message: "user doesn't have the permission." }); } @@ -84,7 +89,10 @@ export const updateRole = async (req: Request, res: Response) => { const isOrgRole = !workspaceId; // if workspaceid is provided then its a workspace rule if (isOrgRole) { - const { permission } = await getUserOrgPermissions(req.user.id, orgId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(orgId) + }); if (permission.cannot(OrgPermissionActions.Edit, OrgPermissionSubjects.Role)) { throw BadRequestError({ message: "User doesn't have the org permission." }); } @@ -140,7 +148,10 @@ export const deleteRole = async (req: Request, res: Response) => { const isOrgRole = !role.workspace; if (isOrgRole) { - const { permission } = await getUserOrgPermissions(req.user.id, role.organization.toString()); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: role.organization + }); if (permission.cannot(OrgPermissionActions.Delete, OrgPermissionSubjects.Role)) { throw BadRequestError({ message: "User doesn't have the org permission." }); } @@ -172,7 +183,10 @@ export const getRoles = async (req: Request, res: Response) => { const isOrgRole = !workspaceId; if (isOrgRole) { - const { permission } = await getUserOrgPermissions(req.user.id, orgId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(orgId) + }); if (permission.cannot(OrgPermissionActions.Read, OrgPermissionSubjects.Role)) { throw BadRequestError({ message: "User doesn't have the org permission." }); } @@ -238,7 +252,7 @@ export const getUserPermissions = async (req: Request, res: Response) => { const { params: { orgId } } = await validateRequest(GetUserPermission, req); - + const { permission, membership } = await getUserOrgPermissions(req.user._id, orgId); res.status(200).json({ diff --git a/backend/src/ee/controllers/v1/ssoController.ts b/backend/src/ee/controllers/v1/ssoController.ts index b7ae793ab..29ed9c18e 100644 --- a/backend/src/ee/controllers/v1/ssoController.ts +++ b/backend/src/ee/controllers/v1/ssoController.ts @@ -13,7 +13,7 @@ import { validateRequest } from "../../../helpers/validation"; import { OrgPermissionActions, OrgPermissionSubjects, - getUserOrgPermissions + getAuthDataOrgPermissions } from "../../services/RoleService"; import { ForbiddenError } from "@casl/ability"; @@ -47,7 +47,10 @@ export const getSSOConfig = async (req: Request, res: Response) => { query: { organizationId } } = await validateRequest(reqValidator.GetSsoConfigv1, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Read, OrgPermissionSubjects.Sso @@ -71,7 +74,10 @@ export const updateSSOConfig = async (req: Request, res: Response) => { body: { organizationId, authProvider, isActive, entryPoint, issuer, cert } } = await validateRequest(reqValidator.UpdateSsoConfigv1, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Edit, OrgPermissionSubjects.Sso @@ -206,7 +212,10 @@ export const createSSOConfig = async (req: Request, res: Response) => { body: { organizationId, authProvider, isActive, entryPoint, issuer, cert } } = await validateRequest(reqValidator.CreateSsoConfigv1, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Create, OrgPermissionSubjects.Sso diff --git a/backend/src/ee/controllers/v3/machineIdentityController.ts b/backend/src/ee/controllers/v3/machineIdentityController.ts index 4057a84b2..a2b8c02e7 100644 --- a/backend/src/ee/controllers/v3/machineIdentityController.ts +++ b/backend/src/ee/controllers/v3/machineIdentityController.ts @@ -22,8 +22,8 @@ import { validateRequest } from "../../../helpers/validation"; import * as reqValidator from "../../../validation/machineIdentity"; import { createToken } from "../../../helpers/auth"; import { - getOrgRolePermissions, - getUserOrgPermissions, + getAuthDataOrgPermissions, + getOrgRolePermissions, isAtLeastAsPrivilegedOrg } from "../../services/RoleService"; import { @@ -76,8 +76,10 @@ export const getMIClientSecrets = async (req: Request, res: Response) => { if (!machineMembershipOrg) throw ResourceNotFoundError(); - const { permission } = await getUserOrgPermissions(req.user._id, machineMembershipOrg.organization.toString()); - + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: machineMembershipOrg.organization + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Read, OrgPermissionSubjects.MachineIdentity @@ -146,7 +148,10 @@ export const createMIClientSecret = async (req: Request, res: Response) => { if (!machineMembershipOrg) throw ResourceNotFoundError(); - const { permission } = await getUserOrgPermissions(req.user._id, machineMembershipOrg.organization.toString()); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: machineMembershipOrg.organization + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Create, @@ -225,7 +230,10 @@ export const deleteMIClientSecret = async (req: Request, res: Response) => { message: `Failed to find machine identity with id ${machineId}` }); - const { permission } = await getUserOrgPermissions(req.user._id, machineMembershipOrg.organization.toString()); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: machineMembershipOrg.organization + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Delete, @@ -434,7 +442,10 @@ export const createMachineIdentity = async (req: Request, res: Response) => { } } = await validateRequest(reqValidator.CreateMachineIdentityV3, req); - const { permission } = await getUserOrgPermissions(req.user._id, organizationId); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: new Types.ObjectId(organizationId) + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Create, @@ -567,7 +578,10 @@ export const updateMachineIdentity = async (req: Request, res: Response) => { message: `Failed to find machine identity with id ${machineId}` }); - const { permission } = await getUserOrgPermissions(req.user._id, machineMembershipOrg.organization.toString()); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: machineMembershipOrg.organization + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Edit, OrgPermissionSubjects.MachineIdentity @@ -725,7 +739,10 @@ export const deleteMachineIdentity = async (req: Request, res: Response) => { message: `Failed to find machine identity with id ${machineId}` }); - const { permission } = await getUserOrgPermissions(req.user._id, machineMembershipOrg.organization.toString()); + const { permission } = await getAuthDataOrgPermissions({ + authData: req.authData, + organizationId: machineMembershipOrg.organization + }); ForbiddenError.from(permission).throwUnlessCan( OrgPermissionActions.Delete, OrgPermissionSubjects.MachineIdentity diff --git a/backend/src/ee/models/auditLog/enums.ts b/backend/src/ee/models/auditLog/enums.ts index f3ffb8e51..14d67f5b9 100644 --- a/backend/src/ee/models/auditLog/enums.ts +++ b/backend/src/ee/models/auditLog/enums.ts @@ -1,4 +1,4 @@ -export enum ActorType { +export enum ActorType { // would extend to AWS, Azure, ... USER = "user", SERVICE = "service", MACHINE = "machine" diff --git a/backend/src/ee/services/RoleService.ts b/backend/src/ee/services/RoleService.ts index 018520915..d68075121 100644 --- a/backend/src/ee/services/RoleService.ts +++ b/backend/src/ee/services/RoleService.ts @@ -1,10 +1,16 @@ import { Types } from "mongoose"; import { AbilityBuilder, MongoAbility, RawRuleOf, createMongoAbility } from "@casl/ability"; -import { MembershipOrg } from "../../models"; -import { IRole, Role } from "../models"; +import { + IMachineIdentity, + MachineMembershipOrg, + MembershipOrg +} from "../../models"; +import { ActorType, IRole, Role } from "../models"; import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors"; +import { checkIPAgainstBlocklist } from "../../utils/ip"; import { ACCEPTED, ADMIN, CUSTOM, MEMBER, NO_ACCESS} from "../../variables"; import { conditionsMatcher } from "./ProjectRoleService"; +import { AuthData } from "../../interfaces/middleware"; export enum OrgPermissionActions { Read = "read", @@ -156,6 +162,94 @@ export const getUserOrgPermissions = async (userId: string, orgId: string) => { throw BadRequestError({ message: "User role not found" }); }; +/** + * Return permissions for user/service pertaining to organization with id [organizationId] + * + * Note: should not rely on this function for ST V2 authorization logic + * b/c ST V2 does not support role-based access control but also not organization-level resources + */ + export const getAuthDataOrgPermissions = async ({ + authData, + organizationId +}: { + authData: AuthData; + organizationId: Types.ObjectId; +}) => { + let role: "admin" | "member" | "no-access" | "custom"; + let customRole; + + switch (authData.actor.type) { + case ActorType.USER: { + const membershipOrg = await MembershipOrg.findOne({ + user: authData.authPayload._id, + organization: organizationId, + status: ACCEPTED + }) + .populate<{ customRole: IRole & { permissions: RawRuleOf>[] } }>( + "customRole" + ) + .exec(); + + if (!membershipOrg || (membershipOrg.role === "custom" && !membershipOrg.customRole)) { + throw UnauthorizedRequestError({ message: "User doesn't belong to organization" }); + } + + role = membershipOrg.role; + customRole = membershipOrg.customRole; + break; + } + case ActorType.SERVICE: { + throw UnauthorizedRequestError({ + message: "Failed to access organization-level resources with service token" + }); + } + case ActorType.MACHINE: { + const machineMembershipOrg = await MachineMembershipOrg.findOne({ + machineIdentity: authData.authPayload._id, + organization: organizationId + }) + .populate<{ + customRole: IRole & { permissions: RawRuleOf>[] }; + machineIdentity: IMachineIdentity + }>("customRole machineIdentity") + .exec(); + + if (!machineMembershipOrg || (machineMembershipOrg.role === "custom" && !machineMembershipOrg.customRole)) { + throw UnauthorizedRequestError(); + } + + checkIPAgainstBlocklist({ + ipAddress: authData.ipAddress, + trustedIps: machineMembershipOrg.machineIdentity.accessTokenTrustedIps + }); + + role = machineMembershipOrg.role; + customRole = machineMembershipOrg.customRole; + break; + } + default: + throw UnauthorizedRequestError(); + } + + switch (role) { + case ADMIN: + return { permission: adminPermissions }; + case MEMBER: + return { permission: memberPermissions }; + case NO_ACCESS: + return { permission: noAccessPermissions }; + case CUSTOM: { + if (!customRole) throw UnauthorizedRequestError(); + return { + permission: createMongoAbility( + customRole.permissions, + { conditionsMatcher } + ) + }; + } + } +} + export const getOrgRolePermissions = async (role: string, orgId: string) => { const isCustomRole = ![ADMIN, MEMBER, NO_ACCESS].includes(role); if (isCustomRole) { diff --git a/backend/src/models/machineMembershipOrg.ts b/backend/src/models/machineMembershipOrg.ts index 5be4a3e53..2a36618da 100644 --- a/backend/src/models/machineMembershipOrg.ts +++ b/backend/src/models/machineMembershipOrg.ts @@ -5,7 +5,7 @@ export interface IMachineMembershipOrg { _id: Types.ObjectId; machineIdentity: Types.ObjectId; organization: Types.ObjectId; - role: "admin" | "member" | "viewer" | "no-access" | "custom"; + role: "admin" | "member" | "no-access" | "custom"; customRole: Types.ObjectId; } diff --git a/backend/src/models/membershipOrg.ts b/backend/src/models/membershipOrg.ts index d83b58e02..0d4a2f6b7 100644 --- a/backend/src/models/membershipOrg.ts +++ b/backend/src/models/membershipOrg.ts @@ -6,7 +6,7 @@ export interface IMembershipOrg extends Document { user: Types.ObjectId; inviteEmail: string; organization: Types.ObjectId; - role: "owner" | "admin" | "member" | "no-access" | "custom"; + role: "admin" | "member" | "no-access" | "custom"; customRole: Types.ObjectId; status: "invited" | "accepted"; }