Merge pull request #1184 from Infisical/stv3-roles

Add role-based project access controls to ST V3
This commit is contained in:
BlackMagiq
2023-11-21 12:26:40 +02:00
committed by GitHub
52 changed files with 1098 additions and 2566 deletions
+11 -3
View File
@@ -7,7 +7,7 @@ import * as reqValidator from "../../validation/bot";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability";
import { BadRequestError } from "../../utils/errors";
@@ -28,7 +28,11 @@ export const getBotByWorkspaceId = async (req: Request, res: Response) => {
const {
params: { workspaceId }
} = await validateRequest(reqValidator.GetBotByWorkspaceIdV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
@@ -70,7 +74,11 @@ export const setBotActiveState = async (req: Request, res: Response) => {
}
const userId = req.user._id;
const { permission } = await getUserProjectPermissions(userId, bot.workspace.toString());
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: bot.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Integrations
@@ -25,7 +25,7 @@ import * as reqValidator from "../../validation/integrationAuth";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability";
import { getIntegrationAuthAccessHelper } from "../../helpers";
@@ -40,15 +40,15 @@ export const getIntegrationAuth = async (req: Request, res: Response) => {
const integrationAuth = await IntegrationAuth.findById(integrationAuthId);
if (!integrationAuth)
return res.status(400).send({
message: "Failed to find integration authorization"
});
if (!integrationAuth) return res.status(400).send({
message: "Failed to find integration authorization"
});
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integrationAuth.workspace
});
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
@@ -79,7 +79,11 @@ export const oAuthExchange = async (req: Request, res: Response) => {
} = await validateRequest(reqValidator.OauthExchangeV1, req);
if (!INTEGRATION_SET.has(integration)) throw new Error("Failed to validate integration");
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Integrations
@@ -131,7 +135,11 @@ export const saveIntegrationToken = async (req: Request, res: Response) => {
body: { workspaceId, integration, url, accessId, namespace, accessToken, refreshToken }
} = await validateRequest(reqValidator.SaveIntegrationAccessTokenV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Integrations
@@ -224,11 +232,12 @@ export const getIntegrationAuthApps = async (req: Request, res: Response) => {
const { integrationAuth, accessToken, accessId } = await getIntegrationAuthAccessHelper({
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integrationAuth.workspace
});
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
@@ -263,10 +272,11 @@ export const getIntegrationAuthTeams = async (req: Request, res: Response) => {
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integrationAuth.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
@@ -299,10 +309,11 @@ export const getIntegrationAuthVercelBranches = async (req: Request, res: Respon
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integrationAuth.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
@@ -360,10 +371,11 @@ export const getIntegrationAuthChecklyGroups = async (req: Request, res: Respons
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integrationAuth.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
@@ -413,10 +425,11 @@ export const getIntegrationAuthQoveryOrgs = async (req: Request, res: Response)
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integrationAuth.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
@@ -465,10 +478,11 @@ export const getIntegrationAuthQoveryProjects = async (req: Request, res: Respon
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integrationAuth.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
@@ -526,10 +540,11 @@ export const getIntegrationAuthQoveryEnvironments = async (req: Request, res: Re
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integrationAuth.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
@@ -587,10 +602,11 @@ export const getIntegrationAuthQoveryApps = async (req: Request, res: Response)
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integrationAuth.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
@@ -648,10 +664,11 @@ export const getIntegrationAuthQoveryContainers = async (req: Request, res: Resp
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integrationAuth.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
@@ -709,10 +726,11 @@ export const getIntegrationAuthQoveryJobs = async (req: Request, res: Response)
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integrationAuth.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
@@ -771,10 +789,11 @@ export const getIntegrationAuthRailwayEnvironments = async (req: Request, res: R
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integrationAuth.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
@@ -864,10 +883,11 @@ export const getIntegrationAuthRailwayServices = async (req: Request, res: Respo
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integrationAuth.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
@@ -988,10 +1008,11 @@ export const getIntegrationAuthBitBucketWorkspaces = async (req: Request, res: R
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integrationAuth.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
@@ -1044,10 +1065,11 @@ export const getIntegrationAuthNorthflankSecretGroups = async (req: Request, res
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integrationAuth.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
@@ -1132,10 +1154,11 @@ export const getIntegrationAuthTeamCityBuildConfigs = async (req: Request, res:
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integrationAuth.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
@@ -1201,10 +1224,11 @@ export const deleteIntegrationAuth = async (req: Request, res: Response) => {
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integrationAuth.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.Integrations
@@ -13,7 +13,7 @@ import * as reqValidator from "../../validation/integration";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability";
@@ -51,11 +51,12 @@ export const createIntegration = async (req: Request, res: Response) => {
);
if (!integrationAuth) throw BadRequestError({ message: "Integration auth not found" });
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace._id.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integrationAuth.workspace._id
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Integrations
@@ -164,10 +165,11 @@ export const updateIntegration = async (req: Request, res: Response) => {
const integration = await Integration.findById(integrationId);
if (!integration) throw BadRequestError({ message: "Integration not found" });
const { permission } = await getUserProjectPermissions(
req.user._id,
integration.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integration.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Integrations
@@ -234,10 +236,11 @@ export const deleteIntegration = async (req: Request, res: Response) => {
const integration = await Integration.findById(integrationId);
if (!integration) throw BadRequestError({ message: "Integration not found" });
const { permission } = await getUserProjectPermissions(
req.user._id,
integration.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integration.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.Integrations
@@ -285,7 +288,11 @@ export const manualSync = async (req: Request, res: Response) => {
body: { workspaceId, environment }
} = await validateRequest(reqValidator.ManualSyncV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Integrations
+6 -2
View File
@@ -9,7 +9,7 @@ import * as reqValidator from "../../validation/key";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability";
@@ -26,7 +26,11 @@ export const uploadKey = async (req: Request, res: Response) => {
body: { key }
} = await validateRequest(reqValidator.UploadKeyV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Member
@@ -12,7 +12,7 @@ import * as reqValidator from "../../validation/membership";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability";
import { BadRequestError } from "../../utils/errors";
@@ -63,11 +63,12 @@ export const deleteMembership = async (req: Request, res: Response) => {
if (!membershipToDelete) {
throw new Error("Failed to delete workspace membership that doesn't exist");
}
const { permission } = await getUserProjectPermissions(
req.user._id,
membershipToDelete.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: membershipToDelete.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.Member
@@ -118,10 +119,11 @@ export const changeMembershipRole = async (req: Request, res: Response) => {
throw new Error("Failed to find membership to change role");
}
const { permission } = await getUserProjectPermissions(
req.user._id,
membershipToChangeRole.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: membershipToChangeRole.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Member
@@ -191,7 +193,12 @@ export const inviteUserToWorkspace = async (req: Request, res: Response) => {
params: { workspaceId },
body: { email }
} = await validateRequest(InviteUserToWorkspaceV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Member
@@ -1,4 +1,6 @@
import { Request, Response } from "express";
import { Types } from "mongoose";
import { isValidScope } from "../../helpers";
import { Folder, IServiceTokenData, SecretImport, ServiceTokenData } from "../../models";
import { getAllImportedSecrets } from "../../services/SecretImportService";
@@ -15,7 +17,7 @@ import * as reqValidator from "../../validation/secretImports";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService";
import { ForbiddenError, subject } from "@casl/ability";
@@ -105,7 +107,11 @@ export const createSecretImp = async (req: Request, res: Response) => {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
}
} else {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory })
@@ -313,10 +319,11 @@ export const updateSecretImport = async (req: Request, res: Response) => {
}
} else {
// non token entry check
const { permission } = await getUserProjectPermissions(
req.user._id,
importSecDoc.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: importSecDoc.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Secrets, {
@@ -442,10 +449,11 @@ export const deleteSecretImport = async (req: Request, res: Response) => {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
}
} else {
const { permission } = await getUserProjectPermissions(
req.user._id,
importSecDoc.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: importSecDoc.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
subject(ProjectPermissionSub.Secrets, {
@@ -550,7 +558,11 @@ export const getSecretImports = async (req: Request, res: Response) => {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
}
} else {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, {
@@ -604,7 +616,11 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
}
} else {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, {
@@ -657,10 +673,11 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => {
permissionCheckFn = (env: string, secPath: string) =>
isValidScope(req.authData.authPayload as IServiceTokenData, env, secPath);
} else {
const { permission } = await getUserProjectPermissions(
req.user._id,
importSecDoc.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: importSecDoc.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, {
@@ -17,7 +17,7 @@ import {
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService";
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
import * as reqValidator from "../../validation/folders";
@@ -125,7 +125,11 @@ export const createFolder = async (req: Request, res: Response) => {
}
} else {
// user check
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory })
@@ -332,7 +336,11 @@ export const updateFolderById = async (req: Request, res: Response) => {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
}
} else {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory })
@@ -502,7 +510,11 @@ export const deleteFolder = async (req: Request, res: Response) => {
}
} else {
// check that user is a member of the workspace
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory })
@@ -649,7 +661,10 @@ export const getFolders = async (req: Request, res: Response) => {
}
} else {
// check that user is a member of the workspace
await getUserProjectPermissions(req.user._id, workspaceId);
await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
}
const folders = await Folder.findOne({ workspace: workspaceId, environment });
+27 -17
View File
@@ -16,7 +16,7 @@ import * as reqValidator from "../../validation/webhooks";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability";
import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto";
@@ -26,7 +26,11 @@ export const createWebhook = async (req: Request, res: Response) => {
body: { webhookUrl, webhookSecretKey, environment, workspaceId, secretPath }
} = await validateRequest(reqValidator.CreateWebhookV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Webhooks
@@ -98,11 +102,11 @@ export const updateWebhook = async (req: Request, res: Response) => {
if (!webhook) {
throw BadRequestError({ message: "Webhook not found!!" });
}
const { permission } = await getUserProjectPermissions(
req.user._id,
webhook.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: webhook.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Webhooks
@@ -146,10 +150,11 @@ export const deleteWebhook = async (req: Request, res: Response) => {
throw ResourceNotFoundError({ message: "Webhook not found!!" });
}
const { permission } = await getUserProjectPermissions(
req.user._id,
webhook.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: webhook.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.Webhooks
@@ -193,10 +198,11 @@ export const testWebhook = async (req: Request, res: Response) => {
throw BadRequestError({ message: "Webhook not found!!" });
}
const { permission } = await getUserProjectPermissions(
req.user._id,
webhook.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: webhook.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Webhooks
@@ -236,8 +242,12 @@ export const listWebhooks = async (req: Request, res: Response) => {
const {
query: { environment, workspaceId, secretPath }
} = await validateRequest(reqValidator.ListWebhooksV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Webhooks
@@ -25,7 +25,7 @@ import * as reqValidator from "../../validation";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService";
/**
@@ -39,7 +39,11 @@ export const getWorkspacePublicKeys = async (req: Request, res: Response) => {
params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspacePublicKeysV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Member
@@ -72,7 +76,11 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => {
params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceMembershipsV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Member
@@ -195,7 +203,11 @@ export const deleteWorkspace = async (req: Request, res: Response) => {
params: { workspaceId }
} = await validateRequest(reqValidator.DeleteWorkspaceV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.Workspace
@@ -223,7 +235,11 @@ export const changeWorkspaceName = async (req: Request, res: Response) => {
body: { name }
} = await validateRequest(reqValidator.ChangeWorkspaceNameV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Workspace
@@ -257,7 +273,12 @@ export const getWorkspaceIntegrations = async (req: Request, res: Response) => {
const {
params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceIntegrationsV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
@@ -283,7 +304,11 @@ export const getWorkspaceIntegrationAuthorizations = async (req: Request, res: R
params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceIntegrationAuthorizationsV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
@@ -309,7 +334,11 @@ export const getWorkspaceServiceTokens = async (req: Request, res: Response) =>
params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceServiceTokensV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.ServiceTokens
@@ -12,14 +12,12 @@ import {
import { EventType, SecretVersion } from "../../ee/models";
import { EEAuditLogService, EELicenseService } from "../../ee/services";
import { BadRequestError, WorkspaceNotFoundError } from "../../utils/errors";
import _ from "lodash";
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../variables";
import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/environments";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability";
import { SecretImport } from "../../models";
@@ -114,7 +112,11 @@ export const createWorkspaceEnvironment = async (req: Request, res: Response) =>
body: { environmentName, environmentSlug }
} = await validateRequest(reqValidator.CreateWorkspaceEnvironmentV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Environments
@@ -191,7 +193,11 @@ export const reorderWorkspaceEnvironments = async (req: Request, res: Response)
body: { environmentName, environmentSlug, otherEnvironmentSlug, otherEnvironmentName }
} = await validateRequest(reqValidator.ReorderWorkspaceEnvironmentsV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Environments
@@ -322,7 +328,11 @@ export const renameWorkspaceEnvironment = async (req: Request, res: Response) =>
body: { environmentName, environmentSlug, oldEnvironmentSlug }
} = await validateRequest(reqValidator.UpdateWorkspaceEnvironmentV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Environments
@@ -511,7 +521,11 @@ export const deleteWorkspaceEnvironment = async (req: Request, res: Response) =>
body: { environmentSlug }
} = await validateRequest(reqValidator.DeleteWorkspaceEnvironmentV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.Environments
@@ -587,98 +601,4 @@ export const deleteWorkspaceEnvironment = async (req: Request, res: Response) =>
workspace: workspaceId,
environment: environmentSlug
});
};
// TODO(akhilmhdh) after rbac this can be completely removed
export const getAllAccessibleEnvironmentsOfWorkspace = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Get all accessible environments of a workspace'
#swagger.description = 'Fetch all environments that the user has access to in a specified workspace'
#swagger.security = [{
"apiKeyAuth": []
}]
#swagger.parameters['workspaceId'] = {
"description": "ID of the workspace",
"required": true,
"type": "string",
"in": "path"
}
#swagger.responses[200] = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"accessibleEnvironments": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"example": "Development"
},
"slug": {
"type": "string",
"example": "development"
},
"isWriteDenied": {
"type": "boolean",
"example": false
},
"isReadDenied": {
"type": "boolean",
"example": false
}
}
}
}
},
"description": "List of environments the user has access to in the specified workspace"
}
}
}
}
*/
const {
params: { workspaceId }
} = await validateRequest(reqValidator.GetAllAccessibileEnvironmentsOfWorkspaceV2, req);
const { membership: workspacesUserIsMemberOf } = await getUserProjectPermissions(
req.user._id,
workspaceId
);
const accessibleEnvironments: any = [];
const deniedPermission = workspacesUserIsMemberOf.deniedPermissions;
const relatedWorkspace = await Workspace.findById(workspaceId);
if (!relatedWorkspace) {
throw BadRequestError();
}
relatedWorkspace.environments.forEach((environment) => {
const isReadBlocked = _.some(deniedPermission, {
environmentSlug: environment.slug,
ability: PERMISSION_READ_SECRETS
});
const isWriteBlocked = _.some(deniedPermission, {
environmentSlug: environment.slug,
ability: PERMISSION_WRITE_SECRETS
});
if (isReadBlocked && isWriteBlocked) {
return;
} else {
accessibleEnvironments.push({
name: environment.name,
slug: environment.slug,
isWriteDenied: isWriteBlocked,
isReadDenied: isReadBlocked
});
}
});
res.json({ accessibleEnvironments });
};
};
@@ -8,7 +8,7 @@ import { EEAuditLogService } from "../../ee/services";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService";
import { sendMail } from "../../helpers";
import { validateRequest } from "../../helpers/validation";
@@ -27,7 +27,11 @@ export const addUserToWorkspace = async (req: Request, res: Response) => {
if (!workspace) throw new Error("Failed to find workspace");
// check permission
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Member
@@ -39,7 +39,7 @@ import {
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService";
import { ForbiddenError, subject } from "@casl/ability";
@@ -159,7 +159,11 @@ export const batchSecrets = async (req: Request, res: Response) => {
}
// not using service token using auth
if (!(req.authData.authPayload instanceof ServiceTokenData)) {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
if (createSecrets.length)
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
@@ -11,7 +11,7 @@ import * as reqValidator from "../../validation/serviceTokenData";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability";
import { Types } from "mongoose";
@@ -75,7 +75,12 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
const {
body: { workspaceId, permissions, tag, encryptedKey, scopes, name, expiresIn, iv }
} = await validateRequest(reqValidator.CreateServiceTokenV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.ServiceTokens
@@ -151,10 +156,11 @@ export const deleteServiceTokenData = async (req: Request, res: Response) => {
let serviceTokenData = await ServiceTokenData.findById(serviceTokenDataId);
if (!serviceTokenData) throw BadRequestError({ message: "Service token not found" });
const { permission } = await getUserProjectPermissions(
req.user._id,
serviceTokenData.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: serviceTokenData.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.ServiceTokens
+17 -7
View File
@@ -7,7 +7,7 @@ import { validateRequest } from "../../helpers/validation";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService";
import * as reqValidator from "../../validation/tags";
@@ -17,7 +17,11 @@ export const createWorkspaceTag = async (req: Request, res: Response) => {
params: { workspaceId }
} = await validateRequest(reqValidator.CreateWorkspaceTagsV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Tags
@@ -45,10 +49,11 @@ export const deleteWorkspaceTag = async (req: Request, res: Response) => {
throw BadRequestError();
}
const { permission } = await getUserProjectPermissions(
req.user._id,
tagFromDB.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: tagFromDB.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.Tags
@@ -66,7 +71,12 @@ export const getWorkspaceTags = async (req: Request, res: Response) => {
const {
params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceTagsV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Tags
@@ -16,7 +16,7 @@ import * as reqValidator from "../../validation";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability";
@@ -272,7 +272,11 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => {
params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceMembershipsV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Member
@@ -352,7 +356,11 @@ export const updateWorkspaceMembership = async (req: Request, res: Response) =>
body: { role }
} = await validateRequest(reqValidator.UpdateWorkspaceMembershipsV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Member
@@ -420,7 +428,11 @@ export const deleteWorkspaceMembership = async (req: Request, res: Response) =>
params: { workspaceId, membershipId }
} = await validateRequest(reqValidator.DeleteWorkspaceMembershipsV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.Member
@@ -452,7 +464,11 @@ export const toggleAutoCapitalization = async (req: Request, res: Response) => {
body: { autoCapitalization }
} = await validateRequest(reqValidator.ToggleAutoCapitalizationV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.Settings
+217 -175
View File
@@ -3,11 +3,16 @@ import { Types } from "mongoose";
import { EventService, SecretService } from "../../services";
import { eventPushSecrets } from "../../events";
import { BotService } from "../../services";
import { containsGlobPatterns, isValidScopeV3, repackageSecretToRaw } from "../../helpers/secrets";
import { containsGlobPatterns, repackageSecretToRaw } from "../../helpers/secrets";
import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto";
import { getAllImportedSecrets } from "../../services/SecretImportService";
import { Folder, IMembership, IServiceTokenData, IServiceTokenDataV3 } from "../../models";
import { Permission } from "../../models/serviceTokenDataV3";
import {
Folder,
IServiceTokenData,
Membership,
ServiceTokenData,
User
} from "../../models";
import { getFolderByPath } from "../../services/FolderService";
import { BadRequestError } from "../../utils/errors";
import { validateRequest } from "../../helpers/validation";
@@ -15,13 +20,10 @@ import * as reqValidator from "../../validation/secrets";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService";
import { ForbiddenError, subject } from "@casl/ability";
import {
validateServiceTokenDataClientForWorkspace,
validateServiceTokenDataV3ClientForWorkspace
} from "../../validation";
import { validateServiceTokenDataClientForWorkspace } from "../../validation";
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../variables";
import { ActorType } from "../../ee/models";
import { UnauthorizedRequestError } from "../../utils/errors";
@@ -31,7 +33,6 @@ import {
getSecretPolicyOfBoard
} from "../../ee/services/SecretApprovalService";
import { CommitType } from "../../ee/models/secretApprovalRequest";
import { IRole } from "../../ee/models/role";
const checkSecretsPermission = async ({
authData,
@@ -47,36 +48,31 @@ const checkSecretsPermission = async ({
secretAction: ProjectPermissionActions; // CRUD
}): Promise<{
authVerifier: (env: string, secPath: string) => boolean;
membership?: Omit<IMembership, "customRole"> & { customRole: IRole };
}> => {
let STV2RequiredPermissions = [];
let STV3RequiredPermissions: Permission[] = [];
switch (secretAction) {
case ProjectPermissionActions.Create:
STV2RequiredPermissions = [PERMISSION_WRITE_SECRETS];
STV3RequiredPermissions = [Permission.WRITE];
break;
case ProjectPermissionActions.Read:
STV2RequiredPermissions = [PERMISSION_READ_SECRETS];
STV3RequiredPermissions = [Permission.READ];
break;
case ProjectPermissionActions.Edit:
STV2RequiredPermissions = [PERMISSION_WRITE_SECRETS];
STV3RequiredPermissions = [Permission.WRITE];
break;
case ProjectPermissionActions.Delete:
STV2RequiredPermissions = [PERMISSION_WRITE_SECRETS];
STV3RequiredPermissions = [Permission.WRITE];
break;
}
switch (authData.actor.type) {
case ActorType.USER: {
const { permission, membership } = await getUserProjectPermissions(
authData.actor.metadata.userId,
workspaceId
);
const { permission } = await getAuthDataProjectPermissions({
authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
secretAction,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
@@ -89,8 +85,7 @@ const checkSecretsPermission = async ({
environment: env,
secretPath: secPath
})
),
membership
)
};
}
case ActorType.SERVICE: {
@@ -104,22 +99,24 @@ const checkSecretsPermission = async ({
return { authVerifier: () => true };
}
case ActorType.SERVICE_V3: {
await validateServiceTokenDataV3ClientForWorkspace({
const { permission } = await getAuthDataProjectPermissions({
authData,
serviceTokenData: authData.authPayload as IServiceTokenDataV3,
workspaceId: new Types.ObjectId(workspaceId),
environment,
secretPath,
requiredPermissions: STV3RequiredPermissions
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
secretAction,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
);
return {
authVerifier: (env: string, secPath: string) =>
isValidScopeV3({
authPayload: authData.authPayload as IServiceTokenDataV3,
environment: env,
secretPath: secPath,
requiredPermissions: STV3RequiredPermissions
})
permission.can(
secretAction,
subject(ProjectPermissionSub.Secrets, {
environment: env,
secretPath: secPath
})
)
};
}
default: {
@@ -199,17 +196,20 @@ export const getSecretsRaw = async (req: Request, res: Response) => {
query: { include_imports: includeImports }
} = validatedData;
// if the service token has single scope, it will get all secrets for that scope by default
const serviceTokenDetails: IServiceTokenData = req?.serviceTokenData;
if (
serviceTokenDetails &&
serviceTokenDetails.scopes.length == 1 &&
!containsGlobPatterns(serviceTokenDetails.scopes[0].secretPath)
) {
const scope = serviceTokenDetails.scopes[0];
secretPath = scope.secretPath;
environment = scope.environment;
workspaceId = serviceTokenDetails.workspace.toString();
if (req.authData.authPayload instanceof ServiceTokenData) {
// if the service token has single scope, it will get all secrets for that scope by default
const serviceTokenDetails: IServiceTokenData = req?.serviceTokenData;
if (
serviceTokenDetails &&
serviceTokenDetails.scopes.length == 1 &&
!containsGlobPatterns(serviceTokenDetails.scopes[0].secretPath)
) {
const scope = serviceTokenDetails.scopes[0];
secretPath = scope.secretPath;
environment = scope.environment;
workspaceId = serviceTokenDetails.workspace.toString();
}
}
if (!environment || !workspaceId)
@@ -900,7 +900,7 @@ export const createSecret = async (req: Request, res: Response) => {
params: { secretName }
} = await validateRequest(reqValidator.CreateSecretV3, req);
const { membership } = await checkSecretsPermission({
await checkSecretsPermission({
authData: req.authData,
workspaceId,
environment,
@@ -908,35 +908,42 @@ export const createSecret = async (req: Request, res: Response) => {
secretAction: ProjectPermissionActions.Create
});
if (membership && type !== "personal") {
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
if (secretApprovalPolicy) {
const secretApprovalRequest = await generateSecretApprovalRequest({
workspaceId,
environment,
secretPath,
policy: secretApprovalPolicy,
commiterMembershipId: membership._id.toString(),
authData: req.authData,
data: {
[CommitType.CREATE]: [
{
secretName,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretCommentIV,
secretCommentTag,
secretCommentCiphertext,
skipMultilineEncoding,
secretKeyTag,
secretKeyCiphertext,
secretKeyIV
}
]
}
});
return res.send({ approval: secretApprovalRequest });
if (req.authData.authPayload instanceof User) {
const membership = await Membership.findOne({
user: req.authData.authPayload._id,
workspace: new Types.ObjectId(workspaceId)
});
if (membership && type !== "personal") {
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
if (secretApprovalPolicy) {
const secretApprovalRequest = await generateSecretApprovalRequest({
workspaceId,
environment,
secretPath,
policy: secretApprovalPolicy,
commiterMembershipId: membership._id.toString(),
authData: req.authData,
data: {
[CommitType.CREATE]: [
{
secretName,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretCommentIV,
secretCommentTag,
secretCommentCiphertext,
skipMultilineEncoding,
secretKeyTag,
secretKeyCiphertext,
secretKeyIV
}
]
}
});
return res.send({ approval: secretApprovalRequest });
}
}
}
@@ -1009,7 +1016,7 @@ export const updateSecretByName = async (req: Request, res: Response) => {
throw BadRequestError({ message: "Missing encrypted key" });
}
const { membership } = await checkSecretsPermission({
await checkSecretsPermission({
authData: req.authData,
workspaceId,
environment,
@@ -1017,37 +1024,44 @@ export const updateSecretByName = async (req: Request, res: Response) => {
secretAction: ProjectPermissionActions.Edit
});
if (membership && type !== "personal") {
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
if (secretApprovalPolicy) {
const secretApprovalRequest = await generateSecretApprovalRequest({
workspaceId,
environment,
secretPath,
policy: secretApprovalPolicy,
commiterMembershipId: membership._id.toString(),
authData: req.authData,
data: {
[CommitType.UPDATE]: [
{
secretName,
newSecretName,
secretValueCiphertext,
secretValueIV,
secretValueTag,
tags,
secretCommentIV,
secretCommentTag,
secretCommentCiphertext,
skipMultilineEncoding,
secretKeyTag,
secretKeyCiphertext,
secretKeyIV
}
]
}
});
return res.send({ approval: secretApprovalRequest });
if (req.authData.authPayload instanceof User) {
const membership = await Membership.findOne({
user: req.authData.authPayload._id,
workspace: new Types.ObjectId(workspaceId)
});
if (membership && type !== "personal") {
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
if (secretApprovalPolicy) {
const secretApprovalRequest = await generateSecretApprovalRequest({
workspaceId,
environment,
secretPath,
policy: secretApprovalPolicy,
commiterMembershipId: membership._id.toString(),
authData: req.authData,
data: {
[CommitType.UPDATE]: [
{
secretName,
newSecretName,
secretValueCiphertext,
secretValueIV,
secretValueTag,
tags,
secretCommentIV,
secretCommentTag,
secretCommentCiphertext,
skipMultilineEncoding,
secretKeyTag,
secretKeyCiphertext,
secretKeyIV
}
]
}
});
return res.send({ approval: secretApprovalRequest });
}
}
}
@@ -1097,7 +1111,7 @@ export const deleteSecretByName = async (req: Request, res: Response) => {
params: { secretName }
} = await validateRequest(reqValidator.DeleteSecretByNameV3, req);
const { membership } = await checkSecretsPermission({
await checkSecretsPermission({
authData: req.authData,
workspaceId,
environment,
@@ -1105,25 +1119,32 @@ export const deleteSecretByName = async (req: Request, res: Response) => {
secretAction: ProjectPermissionActions.Delete
});
if (membership && type !== "personal") {
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
if (secretApprovalPolicy) {
const secretApprovalRequest = await generateSecretApprovalRequest({
workspaceId,
environment,
secretPath,
authData: req.authData,
policy: secretApprovalPolicy,
commiterMembershipId: membership._id.toString(),
data: {
[CommitType.DELETE]: [
{
secretName
}
]
}
});
return res.send({ approval: secretApprovalRequest });
if (req.authData.authPayload instanceof User) {
const membership = await Membership.findOne({
user: req.authData.authPayload._id,
workspace: new Types.ObjectId(workspaceId)
});
if (membership && type !== "personal") {
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
if (secretApprovalPolicy) {
const secretApprovalRequest = await generateSecretApprovalRequest({
workspaceId,
environment,
secretPath,
authData: req.authData,
policy: secretApprovalPolicy,
commiterMembershipId: membership._id.toString(),
data: {
[CommitType.DELETE]: [
{
secretName
}
]
}
});
return res.send({ approval: secretApprovalRequest });
}
}
}
@@ -1155,7 +1176,7 @@ export const createSecretByNameBatch = async (req: Request, res: Response) => {
body: { secrets, secretPath, environment, workspaceId }
} = await validateRequest(reqValidator.CreateSecretByNameBatchV3, req);
const { membership } = await checkSecretsPermission({
await checkSecretsPermission({
authData: req.authData,
workspaceId,
environment,
@@ -1163,21 +1184,28 @@ export const createSecretByNameBatch = async (req: Request, res: Response) => {
secretAction: ProjectPermissionActions.Create
});
if (membership) {
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
if (secretApprovalPolicy) {
const secretApprovalRequest = await generateSecretApprovalRequest({
workspaceId,
environment,
secretPath,
authData: req.authData,
policy: secretApprovalPolicy,
commiterMembershipId: membership._id.toString(),
data: {
[CommitType.CREATE]: secrets.filter(({ type }) => type === "shared")
}
});
return res.send({ approval: secretApprovalRequest });
if (req.authData.authPayload instanceof User) {
const membership = await Membership.findOne({
user: req.authData.authPayload._id,
workspace: new Types.ObjectId(workspaceId)
});
if (membership) {
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
if (secretApprovalPolicy) {
const secretApprovalRequest = await generateSecretApprovalRequest({
workspaceId,
environment,
secretPath,
authData: req.authData,
policy: secretApprovalPolicy,
commiterMembershipId: membership._id.toString(),
data: {
[CommitType.CREATE]: secrets.filter(({ type }) => type === "shared")
}
});
return res.send({ approval: secretApprovalRequest });
}
}
}
@@ -1207,7 +1235,7 @@ export const updateSecretByNameBatch = async (req: Request, res: Response) => {
body: { secrets, secretPath, environment, workspaceId }
} = await validateRequest(reqValidator.UpdateSecretByNameBatchV3, req);
const { membership } = await checkSecretsPermission({
await checkSecretsPermission({
authData: req.authData,
workspaceId,
environment,
@@ -1215,21 +1243,28 @@ export const updateSecretByNameBatch = async (req: Request, res: Response) => {
secretAction: ProjectPermissionActions.Edit
});
if (membership) {
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
if (secretApprovalPolicy) {
const secretApprovalRequest = await generateSecretApprovalRequest({
workspaceId,
environment,
secretPath,
policy: secretApprovalPolicy,
commiterMembershipId: membership._id.toString(),
data: {
[CommitType.UPDATE]: secrets.filter(({ type }) => type === "shared")
},
authData: req.authData
});
return res.send({ approval: secretApprovalRequest });
if (req.authData.authPayload instanceof User) {
const membership = await Membership.findOne({
user: req.authData.authPayload._id,
workspace: new Types.ObjectId(workspaceId)
});
if (membership) {
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
if (secretApprovalPolicy) {
const secretApprovalRequest = await generateSecretApprovalRequest({
workspaceId,
environment,
secretPath,
policy: secretApprovalPolicy,
commiterMembershipId: membership._id.toString(),
data: {
[CommitType.UPDATE]: secrets.filter(({ type }) => type === "shared")
},
authData: req.authData
});
return res.send({ approval: secretApprovalRequest });
}
}
}
@@ -1258,8 +1293,8 @@ export const deleteSecretByNameBatch = async (req: Request, res: Response) => {
const {
body: { secrets, secretPath, environment, workspaceId }
} = await validateRequest(reqValidator.DeleteSecretByNameBatchV3, req);
const { membership } = await checkSecretsPermission({
await checkSecretsPermission({
authData: req.authData,
workspaceId,
environment,
@@ -1267,21 +1302,28 @@ export const deleteSecretByNameBatch = async (req: Request, res: Response) => {
secretAction: ProjectPermissionActions.Delete
});
if (membership) {
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
if (secretApprovalPolicy) {
const secretApprovalRequest = await generateSecretApprovalRequest({
workspaceId,
environment,
secretPath,
policy: secretApprovalPolicy,
commiterMembershipId: membership._id.toString(),
data: {
[CommitType.DELETE]: secrets.filter(({ type }) => type === "shared")
},
authData: req.authData
});
return res.send({ approval: secretApprovalRequest });
if (req.authData.authPayload instanceof User) {
const membership = await Membership.findOne({
user: req.authData.authPayload._id,
workspace: new Types.ObjectId(workspaceId)
});
if (membership) {
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
if (secretApprovalPolicy) {
const secretApprovalRequest = await generateSecretApprovalRequest({
workspaceId,
environment,
secretPath,
policy: secretApprovalPolicy,
commiterMembershipId: membership._id.toString(),
data: {
[CommitType.DELETE]: secrets.filter(({ type }) => type === "shared")
},
authData: req.authData
});
return res.send({ approval: secretApprovalRequest });
}
}
}
@@ -1,9 +1,9 @@
import { Request, Response } from "express";
import { Types } from "mongoose";
import { validateRequest } from "../../helpers/validation";
import { Secret, ServiceTokenDataV3 } from "../../models";
import { Membership, Secret, ServiceTokenDataV3, User } from "../../models";
import { SecretService } from "../../services";
import { getUserProjectPermissions } from "../../ee/services/ProjectRoleService";
import { getAuthDataProjectPermissions } from "../../ee/services/ProjectRoleService";
import { UnauthorizedRequestError } from "../../utils/errors";
import * as reqValidator from "../../validation/workspace";
@@ -19,9 +19,22 @@ export const getWorkspaceBlindIndexStatus = async (req: Request, res: Response)
params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceBlinkIndexStatusV3, req);
const { membership } = await getUserProjectPermissions(req.user._id, workspaceId);
if (membership.role !== "admin")
throw UnauthorizedRequestError({ message: "User must be an admin" });
await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
if (req.authData.authPayload instanceof User) {
const membership = await Membership.findOne({
user: req.authData.authPayload._id,
workspace: new Types.ObjectId(workspaceId)
});
if (!membership) throw UnauthorizedRequestError();
if (membership.role !== "admin")
throw UnauthorizedRequestError({ message: "User must be an admin" });
}
const secretsWithoutBlindIndex = await Secret.countDocuments({
workspace: new Types.ObjectId(workspaceId),
@@ -41,9 +54,22 @@ export const getWorkspaceSecrets = async (req: Request, res: Response) => {
params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceSecretsV3, req);
const { membership } = await getUserProjectPermissions(req.user._id, workspaceId);
if (membership.role !== "admin")
throw UnauthorizedRequestError({ message: "User must be an admin" });
await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
if (req.authData.authPayload instanceof User) {
const membership = await Membership.findOne({
user: req.authData.authPayload._id,
workspace: new Types.ObjectId(workspaceId)
});
if (!membership) throw UnauthorizedRequestError();
if (membership.role !== "admin")
throw UnauthorizedRequestError({ message: "User must be an admin" });
}
const secrets = await Secret.find({
workspace: new Types.ObjectId(workspaceId)
@@ -65,9 +91,22 @@ export const nameWorkspaceSecrets = async (req: Request, res: Response) => {
body: { secretsToUpdate }
} = await validateRequest(reqValidator.NameWorkspaceSecretsV3, req);
const { membership } = await getUserProjectPermissions(req.user._id, workspaceId);
if (membership.role !== "admin")
throw UnauthorizedRequestError({ message: "User must be an admin" });
await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
if (req.authData.authPayload instanceof User) {
const membership = await Membership.findOne({
user: req.authData.authPayload._id,
workspace: new Types.ObjectId(workspaceId)
});
if (!membership) throw UnauthorizedRequestError();
if (membership.role !== "admin")
throw UnauthorizedRequestError({ message: "User must be an admin" });
}
// get secret blind index salt
const salt = await SecretService.getSecretBlindIndexSalt({
@@ -109,7 +148,7 @@ export const getWorkspaceServiceTokenData = async (req: Request, res: Response)
const serviceTokenData = await ServiceTokenDataV3.find({
workspace: new Types.ObjectId(workspaceId)
});
}).populate("customRole");
return res.status(200).send({
serviceTokenData
@@ -1,4 +1,6 @@
import { Request, Response } from "express";
import { Types } from "mongoose";
import { Membership, User } from "../../../models";
import {
CreateRoleSchema,
DeleteRoleSchema,
@@ -11,7 +13,7 @@ import {
ProjectPermissionActions,
ProjectPermissionSub,
adminProjectPermissions,
getUserProjectPermissions,
getAuthDataProjectPermissions,
memberProjectPermissions,
viewerProjectPermission
} from "../../services/ProjectRoleService";
@@ -39,7 +41,10 @@ export const createRole = async (req: Request, res: Response) => {
throw BadRequestError({ message: "user doesn't have the permission." });
}
} else {
const { permission } = await getUserProjectPermissions(req.user.id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
if (permission.cannot(ProjectPermissionActions.Create, ProjectPermissionSub.Role)) {
throw BadRequestError({ message: "User doesn't have the permission." });
}
@@ -82,7 +87,11 @@ export const updateRole = async (req: Request, res: Response) => {
throw BadRequestError({ message: "User doesn't have the org permission." });
}
} else {
const { permission } = await getUserProjectPermissions(req.user.id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
if (permission.cannot(ProjectPermissionActions.Edit, ProjectPermissionSub.Role)) {
throw BadRequestError({ message: "User doesn't have the workspace permission." });
}
@@ -134,7 +143,11 @@ export const deleteRole = async (req: Request, res: Response) => {
throw BadRequestError({ message: "User doesn't have the org permission." });
}
} else {
const { permission } = await getUserProjectPermissions(req.user.id, role.workspace.toString());
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: role.workspace
});
if (permission.cannot(ProjectPermissionActions.Delete, ProjectPermissionSub.Role)) {
throw BadRequestError({ message: "User doesn't have the workspace permission." });
}
@@ -162,7 +175,11 @@ export const getRoles = async (req: Request, res: Response) => {
throw BadRequestError({ message: "User doesn't have the org permission." });
}
} else {
const { permission } = await getUserProjectPermissions(req.user.id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
if (permission.cannot(ProjectPermissionActions.Read, ProjectPermissionSub.Role)) {
throw BadRequestError({ message: "User doesn't have the workspace permission." });
}
@@ -227,7 +244,19 @@ export const getUserWorkspacePermissions = async (req: Request, res: Response) =
const {
params: { workspaceId }
} = await validateRequest(GetUserProjectPermission, req);
const { permission, membership } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
let membership;
if (req.authData.authPayload instanceof User) {
membership = await Membership.findOne({
user: req.authData.authPayload._id,
workspace: new Types.ObjectId(workspaceId)
})
}
res.status(200).json({
data: {
@@ -1,10 +1,11 @@
import { Types } from "mongoose";
import { ForbiddenError, subject } from "@casl/ability";
import { Request, Response } from "express";
import { nanoid } from "nanoid";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
getAuthDataProjectPermissions
} from "../../services/ProjectRoleService";
import { validateRequest } from "../../../helpers/validation";
import { SecretApprovalPolicy } from "../../models/secretApprovalPolicy";
@@ -19,7 +20,11 @@ export const createSecretApprovalPolicy = async (req: Request, res: Response) =>
body: { approvals, secretPath, approvers, environment, workspaceId, name }
} = await validateRequest(reqValidator.CreateSecretApprovalRule, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SecretApproval
@@ -49,10 +54,11 @@ export const updateSecretApprovalPolicy = async (req: Request, res: Response) =>
const secretApproval = await SecretApprovalPolicy.findById(id);
if (!secretApproval) throw ERR_SECRET_APPROVAL_NOT_FOUND;
const { permission } = await getUserProjectPermissions(
req.user._id,
secretApproval.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: secretApproval.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.SecretApproval
@@ -78,10 +84,11 @@ export const deleteSecretApprovalPolicy = async (req: Request, res: Response) =>
const secretApproval = await SecretApprovalPolicy.findById(id);
if (!secretApproval) throw ERR_SECRET_APPROVAL_NOT_FOUND;
const { permission } = await getUserProjectPermissions(
req.user._id,
secretApproval.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: secretApproval.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.SecretApproval
@@ -99,7 +106,11 @@ export const getSecretApprovalPolicy = async (req: Request, res: Response) => {
query: { workspaceId }
} = await validateRequest(reqValidator.GetSecretApprovalRuleList, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.SecretApproval
@@ -117,7 +128,11 @@ export const getSecretApprovalPolicyOfBoard = async (req: Request, res: Response
query: { workspaceId, environment, secretPath }
} = await validateRequest(reqValidator.GetSecretApprovalPolicyOfABoard, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, { secretPath, environment })
@@ -1,7 +1,6 @@
import { Request, Response } from "express";
import { getUserProjectPermissions } from "../../services/ProjectRoleService";
import { validateRequest } from "../../../helpers/validation";
import { Folder } from "../../../models";
import { Folder, Membership, User } from "../../../models";
import { ApprovalStatus, SecretApprovalRequest } from "../../models/secretApprovalRequest";
import * as reqValidator from "../../validation/secretApprovalRequest";
import { getFolderWithPathFromId } from "../../../services/FolderService";
@@ -17,7 +16,15 @@ export const getSecretApprovalRequestCount = async (req: Request, res: Response)
query: { workspaceId }
} = await validateRequest(reqValidator.getSecretApprovalRequestCount, req);
const { membership } = await getUserProjectPermissions(req.user._id, workspaceId);
if (!(req.authData.authPayload instanceof User)) return;
const membership = await Membership.findOne({
user: req.authData.authPayload._id,
workspace: new Types.ObjectId(workspaceId)
});
if (!membership) throw UnauthorizedRequestError();
const approvalRequestCount = await SecretApprovalRequest.aggregate([
{
$match: {
@@ -65,7 +72,14 @@ export const getSecretApprovalRequests = async (req: Request, res: Response) =>
query: { status, committer, workspaceId, environment, limit, offset }
} = await validateRequest(reqValidator.getSecretApprovalRequests, req);
const { membership } = await getUserProjectPermissions(req.user._id, workspaceId);
if (!(req.authData.authPayload instanceof User)) return;
const membership = await Membership.findOne({
user: req.authData.authPayload._id,
workspace: new Types.ObjectId(workspaceId)
});
if (!membership) throw UnauthorizedRequestError();
const query = {
workspace: new Types.ObjectId(workspaceId),
@@ -148,10 +162,15 @@ export const getSecretApprovalRequestDetails = async (req: Request, res: Respons
if (!secretApprovalRequest)
throw BadRequestError({ message: "Secret approval request not found" });
const { membership } = await getUserProjectPermissions(
req.user._id,
secretApprovalRequest.workspace.toString()
);
if (!(req.authData.authPayload instanceof User)) return;
const membership = await Membership.findOne({
user: req.authData.authPayload._id,
workspace: secretApprovalRequest.workspace
});
if (!membership) throw UnauthorizedRequestError();
// allow to fetch only if its admin or is the committer or approver
if (
membership.role !== "admin" &&
@@ -190,10 +209,15 @@ export const updateSecretApprovalReviewStatus = async (req: Request, res: Respon
if (!secretApprovalRequest)
throw BadRequestError({ message: "Secret approval request not found" });
const { membership } = await getUserProjectPermissions(
req.user._id,
secretApprovalRequest.workspace.toString()
);
if (!(req.authData.authPayload instanceof User)) return;
const membership = await Membership.findOne({
user: req.authData.authPayload._id,
workspace: secretApprovalRequest.workspace
});
if (!membership) throw UnauthorizedRequestError();
if (
membership.role !== "admin" &&
secretApprovalRequest.committer !== membership.id &&
@@ -227,10 +251,15 @@ export const mergeSecretApprovalRequest = async (req: Request, res: Response) =>
if (!secretApprovalRequest)
throw BadRequestError({ message: "Secret approval request not found" });
const { membership } = await getUserProjectPermissions(
req.user._id,
secretApprovalRequest.workspace.toString()
);
if (!(req.authData.authPayload instanceof User)) return;
const membership = await Membership.findOne({
user: req.authData.authPayload._id,
workspace: secretApprovalRequest.workspace
});
if (!membership) throw UnauthorizedRequestError();
if (
membership.role !== "admin" &&
secretApprovalRequest.committer !== membership.id &&
@@ -272,10 +301,14 @@ export const updateSecretApprovalRequestStatus = async (req: Request, res: Respo
if (!secretApprovalRequest)
throw BadRequestError({ message: "Secret approval request not found" });
const { membership } = await getUserProjectPermissions(
req.user._id,
secretApprovalRequest.workspace.toString()
);
if (!(req.authData.authPayload instanceof User)) return;
const membership = await Membership.findOne({
user: req.authData.authPayload._id,
workspace: secretApprovalRequest.workspace
});
if (!membership) throw UnauthorizedRequestError();
if (
membership.role !== "admin" &&
@@ -5,7 +5,7 @@ import { Folder, Secret } from "../../../models";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
getAuthDataProjectPermissions
} from "../../services/ProjectRoleService";
import { BadRequestError } from "../../../utils/errors";
import * as reqValidator from "../../../validation";
@@ -74,7 +74,11 @@ export const getSecretVersions = async (req: Request, res: Response) => {
throw BadRequestError({ message: "Failed to find secret" });
}
const { permission } = await getUserProjectPermissions(req.user._id, secret.workspace.toString());
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: secret.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.SecretRollback
@@ -157,10 +161,12 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
if (!toBeUpdatedSec) {
throw BadRequestError({ message: "Failed to find secret" });
}
const { permission } = await getUserProjectPermissions(
req.user._id,
toBeUpdatedSec.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: toBeUpdatedSec.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SecretRollback
@@ -1,11 +1,12 @@
import { Request, Response } from "express";
import { Types } from "mongoose";
import { validateRequest } from "../../../helpers/validation";
import * as reqValidator from "../../validation/secretRotation";
import * as secretRotationService from "../../secretRotation/service";
import {
getUserProjectPermissions,
ProjectPermissionActions,
ProjectPermissionSub
ProjectPermissionSub,
getAuthDataProjectPermissions
} from "../../services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability";
@@ -23,7 +24,11 @@ export const createSecretRotation = async (req: Request, res: Response) => {
}
} = await validateRequest(reqValidator.createSecretRotationV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SecretRotation
@@ -49,7 +54,12 @@ export const restartSecretRotations = async (req: Request, res: Response) => {
} = await validateRequest(reqValidator.restartSecretRotationV1, req);
const doc = await secretRotationService.getSecretRotationById({ id });
const { permission } = await getUserProjectPermissions(req.user._id, doc.workspace.toString());
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: doc.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.SecretRotation
@@ -65,7 +75,12 @@ export const deleteSecretRotations = async (req: Request, res: Response) => {
} = await validateRequest(reqValidator.removeSecretRotationV1, req);
const doc = await secretRotationService.getSecretRotationById({ id });
const { permission } = await getUserProjectPermissions(req.user._id, doc.workspace.toString());
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: doc.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.SecretRotation
@@ -80,7 +95,11 @@ export const getSecretRotations = async (req: Request, res: Response) => {
query: { workspaceId }
} = await validateRequest(reqValidator.getSecretRotationV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.SecretRotation
@@ -1,11 +1,12 @@
import { Request, Response } from "express";
import { Types } from "mongoose";
import { validateRequest } from "../../../helpers/validation";
import * as reqValidator from "../../validation/secretRotationProvider";
import * as secretRotationProviderService from "../../secretRotation/service";
import {
getUserProjectPermissions,
ProjectPermissionActions,
ProjectPermissionSub
ProjectPermissionSub,
getAuthDataProjectPermissions
} from "../../services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability";
@@ -14,7 +15,11 @@ export const getProviderTemplates = async (req: Request, res: Response) => {
params: { workspaceId }
} = await validateRequest(reqValidator.getSecretRotationProvidersV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.SecretRotation
@@ -4,7 +4,7 @@ import { validateRequest } from "../../../helpers/validation";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
getAuthDataProjectPermissions
} from "../../services/ProjectRoleService";
import * as reqValidator from "../../../validation/secretSnapshot";
import { ISecretVersion, SecretSnapshot, TFolderRootVersionSchema } from "../../models";
@@ -33,10 +33,11 @@ export const getSecretSnapshot = async (req: Request, res: Response) => {
if (!secretSnapshot) throw new Error("Failed to find secret snapshot");
const { permission } = await getUserProjectPermissions(
req.user._id,
secretSnapshot.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: secretSnapshot.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.SecretRollback
@@ -27,7 +27,6 @@ import {
} from "../../models";
import { EESecretService } from "../../services";
import { getLatestSecretVersionIds } from "../../helpers/secretVersion";
// import Folder, { TFolderSchema } from "../../../models/folder";
import { getFolderByPath, searchByFolderId } from "../../../services/FolderService";
import { EEAuditLogService, EELicenseService } from "../../services";
import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip";
@@ -46,7 +45,7 @@ import {
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
getAuthDataProjectPermissions
} from "../../services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability";
import { BadRequestError } from "../../../utils/errors";
@@ -107,7 +106,11 @@ export const getWorkspaceSecretSnapshots = async (req: Request, res: Response) =
query: { environment, directory, offset, limit }
} = await validateRequest(GetWorkspaceSecretSnapshotsV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.SecretRollback
@@ -148,7 +151,11 @@ export const getWorkspaceSecretSnapshotsCount = async (req: Request, res: Respon
query: { environment, directory }
} = await validateRequest(GetWorkspaceSecretSnapshotsCountV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.SecretRollback
@@ -238,7 +245,11 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
body: { directory, environment, version }
} = await validateRequest(RollbackWorkspaceSecretSnapshotV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SecretRollback
@@ -572,7 +583,11 @@ export const getWorkspaceAuditLogs = async (req: Request, res: Response) => {
params: { workspaceId }
} = await validateRequest(GetWorkspaceAuditLogsV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.AuditLogs
@@ -632,7 +647,11 @@ export const getWorkspaceAuditLogActorFilterOpts = async (req: Request, res: Res
params: { workspaceId }
} = await validateRequest(GetWorkspaceAuditLogActorFilterOptsV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.AuditLogs
@@ -700,7 +719,11 @@ export const getWorkspaceTrustedIps = async (req: Request, res: Response) => {
params: { workspaceId }
} = await validateRequest(GetWorkspaceTrustedIpsV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.IpAllowList
@@ -726,7 +749,11 @@ export const addWorkspaceTrustedIp = async (req: Request, res: Response) => {
body: { comment, isActive, ipAddress: ip }
} = await validateRequest(AddWorkspaceTrustedIpV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.IpAllowList
@@ -792,7 +819,11 @@ export const updateWorkspaceTrustedIp = async (req: Request, res: Response) => {
body: { ipAddress: ip, comment }
} = await validateRequest(UpdateWorkspaceTrustedIpV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.IpAllowList
@@ -884,7 +915,11 @@ export const deleteWorkspaceTrustedIp = async (req: Request, res: Response) => {
params: { workspaceId, trustedIpId }
} = await validateRequest(DeleteWorkspaceTrustedIpV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.IpAllowList
@@ -8,13 +8,11 @@ import {
ServiceTokenDataV3Key,
Workspace
} from "../../../models";
import {
IServiceTokenV3Scope,
IServiceTokenV3TrustedIp
} from "../../../models/serviceTokenDataV3";
import { IServiceTokenV3TrustedIp } from "../../../models/serviceTokenDataV3";
import {
ActorType,
EventType
EventType,
Role
} from "../../models";
import { validateRequest } from "../../../helpers/validation";
import * as reqValidator from "../../../validation/serviceTokenDataV3";
@@ -22,14 +20,14 @@ import { createToken } from "../../../helpers/auth";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getUserProjectPermissions
getAuthDataProjectPermissions
} from "../../services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability";
import { BadRequestError, ResourceNotFoundError, UnauthorizedRequestError } from "../../../utils/errors";
import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip";
import { EEAuditLogService, EELicenseService } from "../../services";
import { getAuthSecret } from "../../../config";
import { AuthTokenType } from "../../../variables";
import { ADMIN, AuthTokenType, CUSTOM, MEMBER, VIEWER } from "../../../variables";
/**
* Return project key for service token V3
@@ -163,7 +161,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
name,
workspaceId,
publicKey,
scopes,
role,
trustedIps,
expiresIn,
accessTokenTTL,
@@ -172,7 +170,11 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
nonce, // for ServiceTokenDataV3Key
}
} = await validateRequest(reqValidator.CreateServiceTokenV3, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.ServiceTokens
@@ -181,6 +183,19 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
const workspace = await Workspace.findById(workspaceId);
if (!workspace) throw BadRequestError({ message: "Workspace not found" });
const isCustomRole = ![ADMIN, MEMBER, VIEWER].includes(role);
let customRole;
if (isCustomRole) {
customRole = await Role.findOne({
slug: role,
isOrgRole: false,
workspace: workspace._id
});
if (!customRole) throw BadRequestError({ message: "Role not found" });
}
const plan = await EELicenseService.getPlan(workspace.organization);
// validate trusted ips
@@ -219,7 +234,8 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
accessTokenUsageCount: 0,
tokenVersion: 1,
trustedIps: reformattedTrustedIps,
scopes,
role: isCustomRole ? CUSTOM : role,
customRole,
isActive,
expiresAt,
accessTokenTTL,
@@ -250,7 +266,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
metadata: {
name,
isActive,
scopes: scopes as Array<IServiceTokenV3Scope>,
role,
trustedIps: reformattedTrustedIps as Array<IServiceTokenV3TrustedIp>,
expiresAt
}
@@ -278,7 +294,7 @@ export const updateServiceTokenData = async (req: Request, res: Response) => {
body: {
name,
isActive,
scopes,
role,
trustedIps,
expiresIn,
accessTokenTTL,
@@ -291,10 +307,10 @@ export const updateServiceTokenData = async (req: Request, res: Response) => {
message: "Service token not found"
});
const { permission } = await getUserProjectPermissions(
req.user._id,
serviceTokenData.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: serviceTokenData.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
@@ -304,6 +320,20 @@ export const updateServiceTokenData = async (req: Request, res: Response) => {
const workspace = await Workspace.findById(serviceTokenData.workspace);
if (!workspace) throw BadRequestError({ message: "Workspace not found" });
let customRole;
if (role) {
const isCustomRole = ![ADMIN, MEMBER, VIEWER].includes(role);
if (isCustomRole) {
customRole = await Role.findOne({
slug: role,
isOrgRole: false,
workspace: workspace._id
});
if (!customRole) throw BadRequestError({ message: "Role not found" });
}
}
const plan = await EELicenseService.getPlan(workspace.organization);
// validate trusted ips
@@ -335,7 +365,15 @@ export const updateServiceTokenData = async (req: Request, res: Response) => {
{
name,
isActive,
scopes,
role: customRole ? CUSTOM : role,
...(customRole ? {
customRole
} : {}),
...(role && !customRole ? { // non-custom role
$unset: {
customRole: 1
}
} : {}),
trustedIps: reformattedTrustedIps,
expiresAt,
accessTokenTTL,
@@ -357,7 +395,7 @@ export const updateServiceTokenData = async (req: Request, res: Response) => {
metadata: {
name: serviceTokenData.name,
isActive,
scopes: scopes as Array<IServiceTokenV3Scope>,
role,
trustedIps: reformattedTrustedIps as Array<IServiceTokenV3TrustedIp>,
expiresAt
}
@@ -388,10 +426,10 @@ export const deleteServiceTokenData = async (req: Request, res: Response) => {
message: "Service token not found"
});
const { permission } = await getUserProjectPermissions(
req.user._id,
serviceTokenData.workspace.toString()
);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: serviceTokenData.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
@@ -415,7 +453,7 @@ export const deleteServiceTokenData = async (req: Request, res: Response) => {
metadata: {
name: serviceTokenData.name,
isActive: serviceTokenData.isActive,
scopes: serviceTokenData.scopes as Array<IServiceTokenV3Scope>,
role: serviceTokenData.role,
trustedIps: serviceTokenData.trustedIps as Array<IServiceTokenV3TrustedIp>,
expiresAt: serviceTokenData.expiresAt
}
+1 -1
View File
@@ -2,7 +2,7 @@ export enum ActorType {
USER = "user",
SERVICE = "service",
SERVICE_V3 = "service-v3",
Machine = "machine"
// Machine = "machine"
}
export enum UserAgentType {
+8 -8
View File
@@ -1,5 +1,5 @@
import { ActorType, EventType } from "./enums";
import { IServiceTokenV3Scope, IServiceTokenV3TrustedIp } from "../../../models/serviceTokenDataV3";
import { IServiceTokenV3TrustedIp } from "../../../models/serviceTokenDataV3";
interface UserActorMetadata {
userId: string;
@@ -26,11 +26,11 @@ export interface ServiceActorV3 {
metadata: ServiceActorMetadata;
}
export interface MachineActor {
type: ActorType.Machine;
}
// export interface MachineActor {
// type: ActorType.Machine;
// }
export type Actor = UserActor | ServiceActor | ServiceActorV3 | MachineActor;
export type Actor = UserActor | ServiceActor | ServiceActorV3;
interface GetSecretsEvent {
type: EventType.GET_SECRETS;
@@ -225,7 +225,7 @@ interface CreateServiceTokenV3Event {
metadata: {
name: string;
isActive: boolean;
scopes: Array<IServiceTokenV3Scope>;
role: string;
trustedIps: Array<IServiceTokenV3TrustedIp>;
expiresAt?: Date;
};
@@ -236,7 +236,7 @@ interface UpdateServiceTokenV3Event {
metadata: {
name?: string;
isActive?: boolean;
scopes?: Array<IServiceTokenV3Scope>;
role?: string;
trustedIps?: Array<IServiceTokenV3TrustedIp>;
expiresAt?: Date;
};
@@ -247,7 +247,7 @@ interface DeleteServiceTokenV3Event {
metadata: {
name: string;
isActive: boolean;
scopes: Array<IServiceTokenV3Scope>;
role: string;
expiresAt?: Date;
trustedIps: Array<IServiceTokenV3TrustedIp>;
};
+89 -27
View File
@@ -1,3 +1,4 @@
import { Types } from "mongoose";
import {
AbilityBuilder,
ForcedSubject,
@@ -6,11 +7,14 @@ import {
buildMongoQueryMatcher,
createMongoAbility
} from "@casl/ability";
import { Membership } from "../../models";
import { IRole } from "../models/role";
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
import { UnauthorizedRequestError } from "../../utils/errors";
import { FieldCondition, FieldInstruction, JsInterpreter } from "@ucast/mongo2js";
import picomatch from "picomatch";
import { AuthData } from "../../interfaces/middleware";
import { ActorType, IRole } from "../models";
import { Membership, ServiceTokenData, ServiceTokenDataV3 } from "../../models";
import { ADMIN, CUSTOM, MEMBER, VIEWER } from "../../variables";
import { checkIPAgainstBlocklist } from "../../utils/ip";
const $glob: FieldInstruction<string> = {
type: "field",
@@ -239,31 +243,89 @@ const buildViewerPermission = () => {
export const viewerProjectPermission = buildViewerPermission();
export const getUserProjectPermissions = async (userId: string, workspaceId: string) => {
// TODO(akhilmhdh): speed this up by pulling from cache later
const membership = await Membership.findOne({
user: userId,
workspace: workspaceId
})
.populate<{
customRole: IRole & { permissions: RawRuleOf<MongoAbility<ProjectPermissionSet>>[] };
}>("customRole")
.exec();
/**
* Return permissions for user/service pertaining to workspace with id [workspaceId]
*
* Note: should not rely on this function for ST V2 authorization logic
* b/c ST V2 does not support role-based access control
*/
export const getAuthDataProjectPermissions = async ({
authData,
workspaceId
}: {
authData: AuthData;
workspaceId: Types.ObjectId;
}) => {
let role: "admin" | "member" | "viewer" | "custom";
let customRole;
switch (authData.actor.type) {
case ActorType.USER: {
const membership = await Membership.findOne({
user: authData.authPayload._id,
workspace: workspaceId
})
.populate<{
customRole: IRole & { permissions: RawRuleOf<MongoAbility<ProjectPermissionSet>>[] };
}>("customRole")
.exec();
if (!membership || (membership.role === "custom" && !membership.customRole)) {
throw UnauthorizedRequestError();
}
role = membership.role;
customRole = membership.customRole;
break;
}
case ActorType.SERVICE: {
const serviceTokenData = await ServiceTokenData.findById(authData.authPayload._id);
if (!serviceTokenData || !serviceTokenData.workspace.equals(workspaceId)) throw UnauthorizedRequestError();
role = "viewer";
break;
}
case ActorType.SERVICE_V3: {
const serviceTokenData = await ServiceTokenDataV3
.findById(authData.authPayload._id)
.populate<{
customRole: IRole & { permissions: RawRuleOf<MongoAbility<ProjectPermissionSet>>[] };
}>("customRole")
.exec();
if (!serviceTokenData || (serviceTokenData.role === "custom" && !serviceTokenData.customRole)) {
throw UnauthorizedRequestError();
}
if (!membership || (membership.role === "custom" && !membership.customRole)) {
throw UnauthorizedRequestError({ message: "User doesn't belong to organization" });
checkIPAgainstBlocklist({
ipAddress: authData.ipAddress,
trustedIps: serviceTokenData.trustedIps
});
role = serviceTokenData.role;
customRole = serviceTokenData.customRole;
break;
}
default:
throw UnauthorizedRequestError();
}
if (membership.role === "admin") return { permission: adminProjectPermissions, membership };
if (membership.role === "member") return { permission: memberProjectPermissions, membership };
if (membership.role === "viewer") return { permission: viewerProjectPermission, membership };
if (membership.role === "custom") {
const permission = createMongoAbility<ProjectPermissionSet>(membership.customRole.permissions, {
conditionsMatcher
});
return { permission, membership };
switch (role) {
case ADMIN:
return { permission: adminProjectPermissions };
case MEMBER:
return { permission: memberProjectPermissions };
case VIEWER:
return { permission: viewerProjectPermission };
case CUSTOM: {
if (!customRole) throw UnauthorizedRequestError();
return {
permission: createMongoAbility<ProjectPermissionSet>(
customRole.permissions,
{ conditionsMatcher }
)
};
}
default:
throw UnauthorizedRequestError();
}
throw BadRequestError({ message: "User role not found" });
};
}
-38
View File
@@ -13,13 +13,11 @@ import {
Folder,
ISecret,
IServiceTokenData,
IServiceTokenDataV3,
Secret,
SecretBlindIndexData,
ServiceTokenData,
TFolderRootSchema
} from "../models";
import { Permission } from "../models/serviceTokenDataV3";
import { EventType, SecretVersion } from "../ee/models";
import {
BadRequestError,
@@ -51,42 +49,6 @@ import picomatch from "picomatch";
import path from "path";
import { getAnImportedSecret } from "../services/SecretImportService";
/**
* Validate scope for service token v3
* @param authPayload
* @param environment
* @param secretPath
* @returns
*/
export const isValidScopeV3 = ({
authPayload,
environment,
secretPath,
requiredPermissions
}: {
authPayload: IServiceTokenDataV3;
environment: string;
secretPath: string;
requiredPermissions: Permission[];
}) => {
const { scopes } = authPayload;
const validScope = scopes.find(
(scope) =>
picomatch.isMatch(secretPath, scope.secretPath, { strictSlashes: false }) &&
scope.environment === environment
);
if (
validScope &&
!requiredPermissions.every((permission) => validScope.permissions.includes(permission))
) {
return false;
}
return Boolean(validScope);
};
/**
* Validate scope for service token v2
* @param authPayload
+10 -32
View File
@@ -1,16 +1,6 @@
import { Document, Schema, Types, model } from "mongoose";
import { IPType } from "../ee/models";
export enum Permission {
READ = "read",
WRITE = "write"
}
export interface IServiceTokenV3Scope {
environment: string;
secretPath: string;
permissions: Permission[];
}
import { ADMIN, CUSTOM, MEMBER, VIEWER } from "../variables";
export interface IServiceTokenV3TrustedIp {
ipAddress: string;
@@ -33,7 +23,8 @@ export interface IServiceTokenDataV3 extends Document {
isRefreshTokenRotationEnabled: boolean;
expiresAt?: Date;
accessTokenTTL: number;
scopes: Array<IServiceTokenV3Scope>;
role: "admin" | "member" | "viewer" | "custom";
customRole: Types.ObjectId;
trustedIps: Array<IServiceTokenV3TrustedIp>;
}
@@ -100,28 +91,15 @@ const serviceTokenDataV3Schema = new Schema(
default: 7200,
required: true
},
scopes: { // TODO: consider switching this out for roles instead
type: [
{
environment: {
type: String,
required: true
},
secretPath: {
type: String,
default: "/",
required: true
},
permissions: {
type: [String],
enum: [Permission.READ, Permission.WRITE],
default: [Permission.READ],
required: true
}
}
],
role: {
type: String,
enum: [ADMIN, MEMBER, VIEWER, CUSTOM],
required: true
},
customRole: {
type: Schema.Types.ObjectId,
ref: "Role"
},
trustedIps: {
type: [
{
-8
View File
@@ -36,12 +36,4 @@ router.delete(
environmentController.deleteWorkspaceEnvironment
);
router.get(
"/:workspaceId/environments",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
environmentController.getAllAccessibleEnvironmentsOfWorkspace
);
export default router;
+6 -79
View File
@@ -1,64 +1,5 @@
import { Types } from "mongoose";
import { IServiceTokenDataV3 } from "../models";
import { Permission } from "../models/serviceTokenDataV3";
import { z } from "zod";
import { UnauthorizedRequestError } from "../utils/errors";
import { isValidScopeV3 } from "../helpers";
import { AuthData } from "../interfaces/middleware";
import { checkIPAgainstBlocklist } from "../utils/ip";
/**
* Validate that service token (client) can access workspace
* with id [workspaceId] and its environment [environment] with required permissions
* [requiredPermissions]
* @param {Object} obj
* @param {ServiceTokenData} obj.serviceTokenData - service token client
* @param {Types.ObjectId} obj.workspaceId - id of workspace to validate against
* @param {String} environment - (optional) environment in workspace to validate against
* @param {String[]} acceptedPermissions - accepted permissions as part of the endpoint
*/
export const validateServiceTokenDataV3ClientForWorkspace = async ({
authData,
serviceTokenData,
workspaceId,
environment,
secretPath = "/",
requiredPermissions
}: {
authData: AuthData;
serviceTokenData: IServiceTokenDataV3;
workspaceId: Types.ObjectId;
environment?: string;
secretPath?: string;
requiredPermissions: Permission[];
}) => {
// validate ST V3 IP address
checkIPAgainstBlocklist({
ipAddress: authData.ipAddress,
trustedIps: serviceTokenData.trustedIps
});
if (!serviceTokenData.workspace.equals(workspaceId)) {
// case: invalid workspaceId passed
throw UnauthorizedRequestError({
message: "Failed service token authorization for the given workspace"
});
}
if (environment) {
const isValid = isValidScopeV3({
authPayload: serviceTokenData,
environment,
secretPath,
requiredPermissions
});
if (!isValid) throw UnauthorizedRequestError({
message: "Failed service token authorization for the given workspace"
});
}
};
import { MEMBER } from "../variables";
export const RefreshTokenV3 = z.object({
body: z.object({
@@ -71,20 +12,14 @@ export const CreateServiceTokenV3 = z.object({
name: z.string().trim(),
workspaceId: z.string().trim(),
publicKey: z.string().trim(),
scopes: z
.object({
permissions: z.enum(["read", "write"]).array(),
environment: z.string().trim(),
secretPath: z.string().trim()
})
.array()
.min(1),
trustedIps: z
role: z.string().trim().min(1).default(MEMBER),
trustedIps: z // TODO: provide default
.object({
ipAddress: z.string().trim(),
})
.array()
.min(1),
.min(1)
.default([{ ipAddress: "0.0.0.0/0" }]),
expiresIn: z.number().optional(),
accessTokenTTL: z.number().int().min(1),
encryptedKey: z.string().trim(),
@@ -100,15 +35,7 @@ export const UpdateServiceTokenV3 = z.object({
body: z.object({
name: z.string().trim().optional(),
isActive: z.boolean().optional(),
scopes: z
.object({
permissions: z.enum(["read", "write"]).array(),
environment: z.string().trim(),
secretPath: z.string().trim()
})
.array()
.min(1)
.optional(),
role: z.string().trim().min(1).optional(),
trustedIps: z
.object({
ipAddress: z.string().trim()