diff --git a/backend/src/controllers/v1/secretImpsController.ts b/backend/src/controllers/v1/secretImpsController.ts index 70275284b..a16d666c4 100644 --- a/backend/src/controllers/v1/secretImpsController.ts +++ b/backend/src/controllers/v1/secretImpsController.ts @@ -1,6 +1,6 @@ import { Request, Response } from "express"; import { isValidScope } from "../../helpers"; -import { Folder, SecretImport, ServiceTokenData } from "../../models"; +import { Folder, IServiceTokenData, SecretImport, ServiceTokenData } from "../../models"; import { getAllImportedSecrets } from "../../services/SecretImportService"; import { getFolderWithPathFromId } from "../../services/FolderService"; import { @@ -117,7 +117,15 @@ export const createSecretImp = async (req: Request, res: Response) => { if (req.authData.authPayload instanceof ServiceTokenData) { // root check - const isValidScopeAccess = isValidScope(req.authData.authPayload, environment, secretPath); + let isValidScopeAccess = isValidScope(req.authData.authPayload, environment, secretPath); + if (!isValidScopeAccess) { + throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); + } + isValidScopeAccess = isValidScope( + req.authData.authPayload, + secretImport.environment, + secretImport.secretPath + ); if (!isValidScopeAccess) { throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); } @@ -125,7 +133,14 @@ export const createSecretImp = async (req: Request, res: Response) => { const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, - subject(ProjectPermissionSub.SecretImports, { environment, secretPath }) + subject(ProjectPermissionSub.Secrets, { environment, secretPath }) + ); + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionActions.Read, + subject(ProjectPermissionSub.Secrets, { + environment: secretImport.environment, + secretPath: secretImport.secretPath + }) ); } @@ -325,7 +340,7 @@ export const updateSecretImport = async (req: Request, res: Response) => { ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, - subject(ProjectPermissionSub.SecretImports, { + subject(ProjectPermissionSub.Secrets, { environment: importSecDoc.environment, secretPath }) @@ -454,7 +469,7 @@ export const deleteSecretImport = async (req: Request, res: Response) => { ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, - subject(ProjectPermissionSub.SecretImports, { + subject(ProjectPermissionSub.Secrets, { environment: importSecDoc.environment, secretPath }) @@ -588,7 +603,7 @@ export const getSecretImports = async (req: Request, res: Response) => { ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, - subject(ProjectPermissionSub.SecretImports, { + subject(ProjectPermissionSub.Secrets, { environment: importSecDoc.environment, secretPath }) @@ -630,6 +645,7 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => { secretPath = folderPath; } + let permissionCheckFn: (env: string, secPath: string) => boolean; // used to pass as callback function to import secret if (req.authData.authPayload instanceof ServiceTokenData) { // check for service token validity const isValidScopeAccess = isValidScope( @@ -640,18 +656,13 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => { if (!isValidScopeAccess) { throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); } + permissionCheckFn = (env: string, secPath: string) => + isValidScope(req.authData.authPayload as IServiceTokenData, env, secPath); } else { const { permission } = await getUserProjectPermissions( req.user._id, importSecDoc.workspace.toString() ); - ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionActions.Read, - subject(ProjectPermissionSub.SecretImports, { - environment: importSecDoc.environment, - secretPath - }) - ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, subject(ProjectPermissionSub.Secrets, { @@ -659,6 +670,14 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => { secretPath }) ); + permissionCheckFn = (env: string, secPath: string) => + permission.can( + ProjectPermissionActions.Read, + subject(ProjectPermissionSub.Secrets, { + environment: env, + secretPath: secPath + }) + ); } await EEAuditLogService.createAuditLog( @@ -677,6 +696,11 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => { } ); - const secrets = await getAllImportedSecrets(workspaceId, environment, folderId); + const secrets = await getAllImportedSecrets( + workspaceId, + environment, + folderId, + permissionCheckFn + ); return res.status(200).json({ secrets }); }; diff --git a/backend/src/controllers/v1/secretsFolderController.ts b/backend/src/controllers/v1/secretsFolderController.ts index bb6ef7d86..b6f60249c 100644 --- a/backend/src/controllers/v1/secretsFolderController.ts +++ b/backend/src/controllers/v1/secretsFolderController.ts @@ -129,7 +129,7 @@ export const createFolder = async (req: Request, res: Response) => { : "/"; ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, - subject(ProjectPermissionSub.Folders, { environment, secretPath }) + subject(ProjectPermissionSub.Secrets, { environment, secretPath }) ); } @@ -357,7 +357,7 @@ export const updateFolderById = async (req: Request, res: Response) => { const secretPath = getFolderWithPathFromId(folders.nodes, parentFolder.id).folderPath; ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, - subject(ProjectPermissionSub.Folders, { environment, secretPath }) + subject(ProjectPermissionSub.Secrets, { environment, secretPath }) ); } @@ -531,7 +531,7 @@ export const deleteFolder = async (req: Request, res: Response) => { const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, - subject(ProjectPermissionSub.Folders, { environment, secretPath }) + subject(ProjectPermissionSub.Secrets, { environment, secretPath }) ); } @@ -682,18 +682,7 @@ export const getFolders = async (req: Request, res: Response) => { const folders = await Folder.findOne({ workspace: workspaceId, environment }); - if (req.user) { - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); - const secretPath = - folders && parentFolderId - ? getFolderWithPathFromId(folders.nodes, parentFolderId).folderPath - : parentFolderPath || "/"; - - ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionActions.Read, - subject(ProjectPermissionSub.Folders, { environment, secretPath }) - ); - } + if (req.user) await getUserProjectPermissions(req.user._id, workspaceId); if (!folders) { res.send({ folders: [], dir: [] }); diff --git a/backend/src/controllers/v2/secretsController.ts b/backend/src/controllers/v2/secretsController.ts index 1a01282cc..91319de05 100644 --- a/backend/src/controllers/v2/secretsController.ts +++ b/backend/src/controllers/v2/secretsController.ts @@ -979,7 +979,13 @@ export const getSecrets = async (req: Request, res: Response) => { // TODO(akhilmhdh) - secret-imp change this to org type let importedSecrets: any[] = []; if (include_imports) { - importedSecrets = await getAllImportedSecrets(workspaceId, environment, folderId as string); + // depreciated + importedSecrets = await getAllImportedSecrets( + workspaceId, + environment, + folderId as string, + () => false + ); } const channel = getUserAgentType(req.headers["user-agent"]); diff --git a/backend/src/controllers/v3/secretsController.ts b/backend/src/controllers/v3/secretsController.ts index c984d37b2..fad50b0ca 100644 --- a/backend/src/controllers/v3/secretsController.ts +++ b/backend/src/controllers/v3/secretsController.ts @@ -3,7 +3,7 @@ import { Types } from "mongoose"; import { EventService, SecretService } from "../../services"; import { eventPushSecrets } from "../../events"; import { BotService } from "../../services"; -import { containsGlobPatterns, repackageSecretToRaw } from "../../helpers/secrets"; +import { containsGlobPatterns, isValidScope, repackageSecretToRaw } from "../../helpers/secrets"; import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto"; import { getAllImportedSecrets } from "../../services/SecretImportService"; import { Folder, IServiceTokenData } from "../../models"; @@ -55,12 +55,21 @@ export const getSecretsRaw = async (req: Request, res: Response) => { secretPath = getFolderWithPathFromId(folder.nodes, folderId).folderPath; } + let permissionCheckFn: (env: string, secPath: string) => boolean; // used to pass as callback function to import secret if (req.user?._id) { const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, subject(ProjectPermissionSub.Secrets, { environment, secretPath }) ); + permissionCheckFn = (env: string, secPath: string) => + permission.can( + ProjectPermissionActions.Read, + subject(ProjectPermissionSub.Secrets, { + environment: env, + secretPath: secPath + }) + ); } else { await validateServiceTokenDataClientForWorkspace({ serviceTokenData: req.authData.authPayload as IServiceTokenData, @@ -69,6 +78,8 @@ export const getSecretsRaw = async (req: Request, res: Response) => { secretPath, requiredPermissions: [PERMISSION_READ_SECRETS] }); + permissionCheckFn = (env: string, secPath: string) => + isValidScope(req.authData.authPayload as IServiceTokenData, env, secPath); } const secrets = await SecretService.getSecrets({ @@ -94,7 +105,12 @@ export const getSecretsRaw = async (req: Request, res: Response) => { } folderId = folder.id; } - const importedSecrets = await getAllImportedSecrets(workspaceId, environment, folderId); + const importedSecrets = await getAllImportedSecrets( + workspaceId, + environment, + folderId, + permissionCheckFn + ); return res.status(200).send({ secrets: secrets.map((secret) => repackageSecretToRaw({ @@ -394,12 +410,21 @@ export const getSecrets = async (req: Request, res: Response) => { secretPath = getFolderWithPathFromId(folder.nodes, folderId).folderPath; } + let permissionCheckFn: (env: string, secPath: string) => boolean; // used to pass as callback function to import secret if (req.user?._id) { const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, subject(ProjectPermissionSub.Secrets, { environment, secretPath }) ); + permissionCheckFn = (env: string, secPath: string) => + permission.can( + ProjectPermissionActions.Read, + subject(ProjectPermissionSub.Secrets, { + environment: env, + secretPath: secPath + }) + ); } else { await validateServiceTokenDataClientForWorkspace({ serviceTokenData: req.authData.authPayload as IServiceTokenData, @@ -408,6 +433,8 @@ export const getSecrets = async (req: Request, res: Response) => { secretPath, requiredPermissions: [PERMISSION_READ_SECRETS] }); + permissionCheckFn = (env: string, secPath: string) => + isValidScope(req.authData.authPayload as IServiceTokenData, env, secPath); } const secrets = await SecretService.getSecrets({ @@ -429,7 +456,12 @@ export const getSecrets = async (req: Request, res: Response) => { } folderId = folder.id; } - const importedSecrets = await getAllImportedSecrets(workspaceId, environment, folderId); + const importedSecrets = await getAllImportedSecrets( + workspaceId, + environment, + folderId, + permissionCheckFn + ); return res.status(200).send({ secrets, imports: importedSecrets diff --git a/backend/src/ee/services/ProjectRoleService.ts b/backend/src/ee/services/ProjectRoleService.ts index c7d1e7494..26491fab2 100644 --- a/backend/src/ee/services/ProjectRoleService.ts +++ b/backend/src/ee/services/ProjectRoleService.ts @@ -49,9 +49,7 @@ export enum ProjectPermissionSub { IpAllowList = "ip-allowlist", Workspace = "workspace", Secrets = "secrets", - SecretImports = "secret-imports", - SecretRollback = "secret-rollback", - Folders = "folders" + SecretRollback = "secret-rollback" } type SubjectFields = { @@ -64,17 +62,6 @@ export type ProjectPermissionSet = ProjectPermissionActions, ProjectPermissionSub.Secrets | (ForcedSubject & SubjectFields) ] - | [ - ProjectPermissionActions, - ProjectPermissionSub.Folders | (ForcedSubject & SubjectFields) - ] - | [ - ProjectPermissionActions, - ( - | ProjectPermissionSub.SecretImports - | (ForcedSubject & SubjectFields) - ) - ] | [ProjectPermissionActions, ProjectPermissionSub.Role] | [ProjectPermissionActions, ProjectPermissionSub.Tags] | [ProjectPermissionActions, ProjectPermissionSub.Member] @@ -98,16 +85,6 @@ const buildAdminPermission = () => { can(ProjectPermissionActions.Edit, ProjectPermissionSub.Secrets); can(ProjectPermissionActions.Delete, ProjectPermissionSub.Secrets); - can(ProjectPermissionActions.Read, ProjectPermissionSub.Folders); - can(ProjectPermissionActions.Create, ProjectPermissionSub.Folders); - can(ProjectPermissionActions.Edit, ProjectPermissionSub.Folders); - can(ProjectPermissionActions.Delete, ProjectPermissionSub.Folders); - - can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretImports); - can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretImports); - can(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretImports); - can(ProjectPermissionActions.Delete, ProjectPermissionSub.SecretImports); - can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback); @@ -177,15 +154,6 @@ const buildMemberPermission = () => { can(ProjectPermissionActions.Edit, ProjectPermissionSub.Secrets); can(ProjectPermissionActions.Delete, ProjectPermissionSub.Secrets); - can(ProjectPermissionActions.Read, ProjectPermissionSub.Folders); - can(ProjectPermissionActions.Create, ProjectPermissionSub.Folders); - can(ProjectPermissionActions.Edit, ProjectPermissionSub.Folders); - can(ProjectPermissionActions.Delete, ProjectPermissionSub.Folders); - - can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretImports); - can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretImports); - can(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretImports); - can(ProjectPermissionActions.Delete, ProjectPermissionSub.SecretImports); can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback); @@ -209,8 +177,6 @@ const buildViewerPermission = () => { const { can, build } = new AbilityBuilder>(createMongoAbility); can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets); - can(ProjectPermissionActions.Read, ProjectPermissionSub.Folders); - can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretImports); can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); can(ProjectPermissionActions.Read, ProjectPermissionSub.Member); can(ProjectPermissionActions.Read, ProjectPermissionSub.Role); diff --git a/backend/src/helpers/bot.ts b/backend/src/helpers/bot.ts index 245fe1192..b9b7aac51 100644 --- a/backend/src/helpers/bot.ts +++ b/backend/src/helpers/bot.ts @@ -134,7 +134,8 @@ export const getSecretsBotHelper = async ({ const importedSecrets = await getAllImportedSecrets( workspaceId.toString(), environment, - folderId + folderId, + () => true // integrations are setup to read all the ones ); importedSecrets.forEach(({ secrets }) => { diff --git a/backend/src/services/SecretImportService.ts b/backend/src/services/SecretImportService.ts index d4442b835..b432976fc 100644 --- a/backend/src/services/SecretImportService.ts +++ b/backend/src/services/SecretImportService.ts @@ -1,10 +1,5 @@ import { Types } from "mongoose"; -import { - Folder, - ISecret, - Secret, - SecretImport -} from "../models"; +import { Folder, ISecret, Secret, SecretImport } from "../models"; import { getFolderByPath } from "./FolderService"; type TSecretImportFid = { environment: string; folderId: string; secretPath: string }; @@ -12,7 +7,8 @@ type TSecretImportFid = { environment: string; folderId: string; secretPath: str export const getAllImportedSecrets = async ( workspaceId: string, environment: string, - folderId = "root" + folderId = "root", + permissionCheckCB: (env: string, secPath: string) => boolean ) => { const secImports = await SecretImport.findOne({ workspace: workspaceId, @@ -23,7 +19,10 @@ export const getAllImportedSecrets = async ( if (secImports.imports.length === 0) return []; const importedEnv: Record = {}; // to get folders from all environment - secImports.imports.forEach((el) => (importedEnv[el.environment] = true)); + const allowedSecretImports = secImports.imports.filter((el) => + permissionCheckCB(el.environment, el.secretPath) + ); + allowedSecretImports.forEach((el) => (importedEnv[el.environment] = true)); const folders = await Folder.find({ workspace: workspaceId, @@ -31,7 +30,7 @@ export const getAllImportedSecrets = async ( }); const importedSecByFid: TSecretImportFid[] = []; - secImports.imports.forEach((el) => { + allowedSecretImports.forEach((el) => { const folder = folders.find((fl) => fl.environment === el.environment); if (folder) { const secPathFolder = getFolderByPath(folder.nodes, el.secretPath); diff --git a/frontend/src/context/ProjectPermissionContext/types.ts b/frontend/src/context/ProjectPermissionContext/types.ts index ca1ca13ae..9a2e89713 100644 --- a/frontend/src/context/ProjectPermissionContext/types.ts +++ b/frontend/src/context/ProjectPermissionContext/types.ts @@ -20,9 +20,7 @@ export enum ProjectPermissionSub { IpAllowList = "ip-allowlist", Workspace = "workspace", Secrets = "secrets", - SecretImports = "secret-imports", - SecretRollback = "secret-rollback", - Folders = "folders" + SecretRollback = "secret-rollback" } type SubjectFields = { @@ -35,17 +33,6 @@ export type ProjectPermissionSet = ProjectPermissionActions, ProjectPermissionSub.Secrets | (ForcedSubject & SubjectFields) ] - | [ - ProjectPermissionActions, - ProjectPermissionSub.Folders | (ForcedSubject & SubjectFields) - ] - | [ - ProjectPermissionActions, - ( - | ProjectPermissionSub.SecretImports - | (ForcedSubject & SubjectFields) - ) - ] | [ProjectPermissionActions, ProjectPermissionSub.Role] | [ProjectPermissionActions, ProjectPermissionSub.Tags] | [ProjectPermissionActions, ProjectPermissionSub.Member] diff --git a/frontend/src/views/DashboardPage/DashboardPage.tsx b/frontend/src/views/DashboardPage/DashboardPage.tsx index 81cbedf03..220ddb146 100644 --- a/frontend/src/views/DashboardPage/DashboardPage.tsx +++ b/frontend/src/views/DashboardPage/DashboardPage.tsx @@ -258,21 +258,7 @@ export const DashboardPage = () => { ?.map(({ name }) => name) .join("/")}`; - const userAvailableEnvs = currentWorkspace?.environments?.filter( - ({ slug }) => - permission.can( - ProjectPermissionActions.Read, - subject(ProjectPermissionSub.Secrets, { environment: slug, secretPath }) - ) || - permission.can( - ProjectPermissionActions.Read, - subject(ProjectPermissionSub.Folders, { environment: slug, secretPath }) - ) || - permission.can( - ProjectPermissionActions.Read, - subject(ProjectPermissionSub.SecretImports, { environment: slug, secretPath }) - ) - ); + const userAvailableEnvs = currentWorkspace?.environments || []; // This is for dnd-kit. As react-query state mutation async // This will act as a placeholder to avoid a glitching animation on dropping items @@ -943,7 +929,7 @@ export const DashboardPage = () => {
{(isAllowed) => (