Merge pull request #972 from akhilmhdh/feat/permission-patch

feat(rbac): grouped folder and imports permission into secret permission
This commit is contained in:
Maidul Islam
2023-09-13 13:33:26 -04:00
committed by GitHub
15 changed files with 110 additions and 174 deletions

View File

@@ -1,6 +1,6 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { isValidScope } from "../../helpers"; import { isValidScope } from "../../helpers";
import { Folder, SecretImport, ServiceTokenData } from "../../models"; import { Folder, IServiceTokenData, SecretImport, ServiceTokenData } from "../../models";
import { getAllImportedSecrets } from "../../services/SecretImportService"; import { getAllImportedSecrets } from "../../services/SecretImportService";
import { getFolderWithPathFromId } from "../../services/FolderService"; import { getFolderWithPathFromId } from "../../services/FolderService";
import { import {
@@ -117,7 +117,15 @@ export const createSecretImp = async (req: Request, res: Response) => {
if (req.authData.authPayload instanceof ServiceTokenData) { if (req.authData.authPayload instanceof ServiceTokenData) {
// root check // 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) { if (!isValidScopeAccess) {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); 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); const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, 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( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.SecretImports, { subject(ProjectPermissionSub.Secrets, {
environment: importSecDoc.environment, environment: importSecDoc.environment,
secretPath secretPath
}) })
@@ -454,7 +469,7 @@ export const deleteSecretImport = async (req: Request, res: Response) => {
); );
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
subject(ProjectPermissionSub.SecretImports, { subject(ProjectPermissionSub.Secrets, {
environment: importSecDoc.environment, environment: importSecDoc.environment,
secretPath secretPath
}) })
@@ -588,7 +603,7 @@ export const getSecretImports = async (req: Request, res: Response) => {
); );
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
subject(ProjectPermissionSub.SecretImports, { subject(ProjectPermissionSub.Secrets, {
environment: importSecDoc.environment, environment: importSecDoc.environment,
secretPath secretPath
}) })
@@ -630,6 +645,7 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => {
secretPath = folderPath; secretPath = folderPath;
} }
let permissionCheckFn: (env: string, secPath: string) => boolean; // used to pass as callback function to import secret
if (req.authData.authPayload instanceof ServiceTokenData) { if (req.authData.authPayload instanceof ServiceTokenData) {
// check for service token validity // check for service token validity
const isValidScopeAccess = isValidScope( const isValidScopeAccess = isValidScope(
@@ -640,18 +656,13 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => {
if (!isValidScopeAccess) { if (!isValidScopeAccess) {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
} }
permissionCheckFn = (env: string, secPath: string) =>
isValidScope(req.authData.authPayload as IServiceTokenData, env, secPath);
} else { } else {
const { permission } = await getUserProjectPermissions( const { permission } = await getUserProjectPermissions(
req.user._id, req.user._id,
importSecDoc.workspace.toString() importSecDoc.workspace.toString()
); );
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.SecretImports, {
environment: importSecDoc.environment,
secretPath
})
);
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, { subject(ProjectPermissionSub.Secrets, {
@@ -659,6 +670,14 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => {
secretPath secretPath
}) })
); );
permissionCheckFn = (env: string, secPath: string) =>
permission.can(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, {
environment: env,
secretPath: secPath
})
);
} }
await EEAuditLogService.createAuditLog( 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 }); return res.status(200).json({ secrets });
}; };

View File

@@ -129,7 +129,7 @@ export const createFolder = async (req: Request, res: Response) => {
: "/"; : "/";
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, 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; const secretPath = getFolderWithPathFromId(folders.nodes, parentFolder.id).folderPath;
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, 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); const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, 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 }); const folders = await Folder.findOne({ workspace: workspaceId, environment });
if (req.user) { if (req.user) await getUserProjectPermissions(req.user._id, workspaceId);
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 (!folders) { if (!folders) {
res.send({ folders: [], dir: [] }); res.send({ folders: [], dir: [] });

View File

@@ -979,7 +979,13 @@ export const getSecrets = async (req: Request, res: Response) => {
// TODO(akhilmhdh) - secret-imp change this to org type // TODO(akhilmhdh) - secret-imp change this to org type
let importedSecrets: any[] = []; let importedSecrets: any[] = [];
if (include_imports) { 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"]); const channel = getUserAgentType(req.headers["user-agent"]);

View File

@@ -3,7 +3,7 @@ import { Types } from "mongoose";
import { EventService, SecretService } from "../../services"; import { EventService, SecretService } from "../../services";
import { eventPushSecrets } from "../../events"; import { eventPushSecrets } from "../../events";
import { BotService } from "../../services"; import { BotService } from "../../services";
import { containsGlobPatterns, repackageSecretToRaw } from "../../helpers/secrets"; import { containsGlobPatterns, isValidScope, repackageSecretToRaw } from "../../helpers/secrets";
import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto"; import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto";
import { getAllImportedSecrets } from "../../services/SecretImportService"; import { getAllImportedSecrets } from "../../services/SecretImportService";
import { Folder, IServiceTokenData } from "../../models"; import { Folder, IServiceTokenData } from "../../models";
@@ -55,12 +55,21 @@ export const getSecretsRaw = async (req: Request, res: Response) => {
secretPath = getFolderWithPathFromId(folder.nodes, folderId).folderPath; 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) { if (req.user?._id) {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, { environment, secretPath }) subject(ProjectPermissionSub.Secrets, { environment, secretPath })
); );
permissionCheckFn = (env: string, secPath: string) =>
permission.can(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, {
environment: env,
secretPath: secPath
})
);
} else { } else {
await validateServiceTokenDataClientForWorkspace({ await validateServiceTokenDataClientForWorkspace({
serviceTokenData: req.authData.authPayload as IServiceTokenData, serviceTokenData: req.authData.authPayload as IServiceTokenData,
@@ -69,6 +78,8 @@ export const getSecretsRaw = async (req: Request, res: Response) => {
secretPath, secretPath,
requiredPermissions: [PERMISSION_READ_SECRETS] requiredPermissions: [PERMISSION_READ_SECRETS]
}); });
permissionCheckFn = (env: string, secPath: string) =>
isValidScope(req.authData.authPayload as IServiceTokenData, env, secPath);
} }
const secrets = await SecretService.getSecrets({ const secrets = await SecretService.getSecrets({
@@ -94,7 +105,12 @@ export const getSecretsRaw = async (req: Request, res: Response) => {
} }
folderId = folder.id; folderId = folder.id;
} }
const importedSecrets = await getAllImportedSecrets(workspaceId, environment, folderId); const importedSecrets = await getAllImportedSecrets(
workspaceId,
environment,
folderId,
permissionCheckFn
);
return res.status(200).send({ return res.status(200).send({
secrets: secrets.map((secret) => secrets: secrets.map((secret) =>
repackageSecretToRaw({ repackageSecretToRaw({
@@ -394,12 +410,21 @@ export const getSecrets = async (req: Request, res: Response) => {
secretPath = getFolderWithPathFromId(folder.nodes, folderId).folderPath; 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) { if (req.user?._id) {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, { environment, secretPath }) subject(ProjectPermissionSub.Secrets, { environment, secretPath })
); );
permissionCheckFn = (env: string, secPath: string) =>
permission.can(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, {
environment: env,
secretPath: secPath
})
);
} else { } else {
await validateServiceTokenDataClientForWorkspace({ await validateServiceTokenDataClientForWorkspace({
serviceTokenData: req.authData.authPayload as IServiceTokenData, serviceTokenData: req.authData.authPayload as IServiceTokenData,
@@ -408,6 +433,8 @@ export const getSecrets = async (req: Request, res: Response) => {
secretPath, secretPath,
requiredPermissions: [PERMISSION_READ_SECRETS] requiredPermissions: [PERMISSION_READ_SECRETS]
}); });
permissionCheckFn = (env: string, secPath: string) =>
isValidScope(req.authData.authPayload as IServiceTokenData, env, secPath);
} }
const secrets = await SecretService.getSecrets({ const secrets = await SecretService.getSecrets({
@@ -429,7 +456,12 @@ export const getSecrets = async (req: Request, res: Response) => {
} }
folderId = folder.id; folderId = folder.id;
} }
const importedSecrets = await getAllImportedSecrets(workspaceId, environment, folderId); const importedSecrets = await getAllImportedSecrets(
workspaceId,
environment,
folderId,
permissionCheckFn
);
return res.status(200).send({ return res.status(200).send({
secrets, secrets,
imports: importedSecrets imports: importedSecrets

View File

@@ -49,9 +49,7 @@ export enum ProjectPermissionSub {
IpAllowList = "ip-allowlist", IpAllowList = "ip-allowlist",
Workspace = "workspace", Workspace = "workspace",
Secrets = "secrets", Secrets = "secrets",
SecretImports = "secret-imports", SecretRollback = "secret-rollback"
SecretRollback = "secret-rollback",
Folders = "folders"
} }
type SubjectFields = { type SubjectFields = {
@@ -64,17 +62,6 @@ export type ProjectPermissionSet =
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub.Secrets | (ForcedSubject<ProjectPermissionSub.Secrets> & SubjectFields) ProjectPermissionSub.Secrets | (ForcedSubject<ProjectPermissionSub.Secrets> & SubjectFields)
] ]
| [
ProjectPermissionActions,
ProjectPermissionSub.Folders | (ForcedSubject<ProjectPermissionSub.Folders> & SubjectFields)
]
| [
ProjectPermissionActions,
(
| ProjectPermissionSub.SecretImports
| (ForcedSubject<ProjectPermissionSub.SecretImports> & SubjectFields)
)
]
| [ProjectPermissionActions, ProjectPermissionSub.Role] | [ProjectPermissionActions, ProjectPermissionSub.Role]
| [ProjectPermissionActions, ProjectPermissionSub.Tags] | [ProjectPermissionActions, ProjectPermissionSub.Tags]
| [ProjectPermissionActions, ProjectPermissionSub.Member] | [ProjectPermissionActions, ProjectPermissionSub.Member]
@@ -98,16 +85,6 @@ const buildAdminPermission = () => {
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Secrets); can(ProjectPermissionActions.Edit, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Delete, 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.Read, ProjectPermissionSub.SecretRollback);
can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback); can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback);
@@ -177,15 +154,6 @@ const buildMemberPermission = () => {
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Secrets); can(ProjectPermissionActions.Edit, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Delete, 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.Read, ProjectPermissionSub.SecretRollback);
can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback); can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback);
@@ -209,8 +177,6 @@ const buildViewerPermission = () => {
const { can, build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility); const { can, build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets); can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Folders);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretImports);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Member); can(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role); can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);

View File

@@ -134,7 +134,8 @@ export const getSecretsBotHelper = async ({
const importedSecrets = await getAllImportedSecrets( const importedSecrets = await getAllImportedSecrets(
workspaceId.toString(), workspaceId.toString(),
environment, environment,
folderId folderId,
() => true // integrations are setup to read all the ones
); );
importedSecrets.forEach(({ secrets }) => { importedSecrets.forEach(({ secrets }) => {

View File

@@ -1,10 +1,5 @@
import { Types } from "mongoose"; import { Types } from "mongoose";
import { import { Folder, ISecret, Secret, SecretImport } from "../models";
Folder,
ISecret,
Secret,
SecretImport
} from "../models";
import { getFolderByPath } from "./FolderService"; import { getFolderByPath } from "./FolderService";
type TSecretImportFid = { environment: string; folderId: string; secretPath: string }; type TSecretImportFid = { environment: string; folderId: string; secretPath: string };
@@ -12,7 +7,8 @@ type TSecretImportFid = { environment: string; folderId: string; secretPath: str
export const getAllImportedSecrets = async ( export const getAllImportedSecrets = async (
workspaceId: string, workspaceId: string,
environment: string, environment: string,
folderId = "root" folderId = "root",
permissionCheckCB: (env: string, secPath: string) => boolean
) => { ) => {
const secImports = await SecretImport.findOne({ const secImports = await SecretImport.findOne({
workspace: workspaceId, workspace: workspaceId,
@@ -23,7 +19,10 @@ export const getAllImportedSecrets = async (
if (secImports.imports.length === 0) return []; if (secImports.imports.length === 0) return [];
const importedEnv: Record<string, boolean> = {}; // to get folders from all environment const importedEnv: Record<string, boolean> = {}; // 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({ const folders = await Folder.find({
workspace: workspaceId, workspace: workspaceId,
@@ -31,7 +30,7 @@ export const getAllImportedSecrets = async (
}); });
const importedSecByFid: TSecretImportFid[] = []; const importedSecByFid: TSecretImportFid[] = [];
secImports.imports.forEach((el) => { allowedSecretImports.forEach((el) => {
const folder = folders.find((fl) => fl.environment === el.environment); const folder = folders.find((fl) => fl.environment === el.environment);
if (folder) { if (folder) {
const secPathFolder = getFolderByPath(folder.nodes, el.secretPath); const secPathFolder = getFolderByPath(folder.nodes, el.secretPath);

View File

@@ -20,9 +20,7 @@ export enum ProjectPermissionSub {
IpAllowList = "ip-allowlist", IpAllowList = "ip-allowlist",
Workspace = "workspace", Workspace = "workspace",
Secrets = "secrets", Secrets = "secrets",
SecretImports = "secret-imports", SecretRollback = "secret-rollback"
SecretRollback = "secret-rollback",
Folders = "folders"
} }
type SubjectFields = { type SubjectFields = {
@@ -35,17 +33,6 @@ export type ProjectPermissionSet =
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub.Secrets | (ForcedSubject<ProjectPermissionSub.Secrets> & SubjectFields) ProjectPermissionSub.Secrets | (ForcedSubject<ProjectPermissionSub.Secrets> & SubjectFields)
] ]
| [
ProjectPermissionActions,
ProjectPermissionSub.Folders | (ForcedSubject<ProjectPermissionSub.Folders> & SubjectFields)
]
| [
ProjectPermissionActions,
(
| ProjectPermissionSub.SecretImports
| (ForcedSubject<ProjectPermissionSub.SecretImports> & SubjectFields)
)
]
| [ProjectPermissionActions, ProjectPermissionSub.Role] | [ProjectPermissionActions, ProjectPermissionSub.Role]
| [ProjectPermissionActions, ProjectPermissionSub.Tags] | [ProjectPermissionActions, ProjectPermissionSub.Tags]
| [ProjectPermissionActions, ProjectPermissionSub.Member] | [ProjectPermissionActions, ProjectPermissionSub.Member]

View File

@@ -258,21 +258,7 @@ export const DashboardPage = () => {
?.map(({ name }) => name) ?.map(({ name }) => name)
.join("/")}`; .join("/")}`;
const userAvailableEnvs = currentWorkspace?.environments?.filter( const userAvailableEnvs = currentWorkspace?.environments || [];
({ 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 })
)
);
// This is for dnd-kit. As react-query state mutation async // 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 // This will act as a placeholder to avoid a glitching animation on dropping items
@@ -943,7 +929,7 @@ export const DashboardPage = () => {
<div className="w-full pb-1"> <div className="w-full pb-1">
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionActions.Create} I={ProjectPermissionActions.Create}
a={ProjectPermissionSub.Folders} a={ProjectPermissionSub.Secrets}
> >
{(isAllowed) => ( {(isAllowed) => (
<Button <Button
@@ -962,7 +948,7 @@ export const DashboardPage = () => {
<div className="w-full"> <div className="w-full">
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionActions.Edit} I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.SecretImports, { a={subject(ProjectPermissionSub.Secrets, {
environment, environment,
secretPath secretPath
})} })}

View File

@@ -56,7 +56,7 @@ export const FolderSection = memo(
<div className="duration-0 flex h-10 w-16 items-center justify-end space-x-2.5 overflow-hidden border-l border-mineshaft-600 transition-all"> <div className="duration-0 flex h-10 w-16 items-center justify-end space-x-2.5 overflow-hidden border-l border-mineshaft-600 transition-all">
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionActions.Edit} I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.Folders, { environment, secretPath })} a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
> >
{(isAllowed) => ( {(isAllowed) => (
<div className="opacity-0 group-hover:opacity-100"> <div className="opacity-0 group-hover:opacity-100">
@@ -77,7 +77,7 @@ export const FolderSection = memo(
</ProjectPermissionCan> </ProjectPermissionCan>
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionActions.Delete} I={ProjectPermissionActions.Delete}
a={subject(ProjectPermissionSub.Folders, { environment, secretPath })} a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
> >
{(isAllowed) => ( {(isAllowed) => (
<div className="opacity-0 group-hover:opacity-100"> <div className="opacity-0 group-hover:opacity-100">

View File

@@ -119,7 +119,7 @@ export const SecretImportItem = ({
</div> </div>
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionActions.Delete} I={ProjectPermissionActions.Delete}
a={subject(ProjectPermissionSub.SecretImports, { environment, secretPath })} a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
> >
{(isAllowed) => ( {(isAllowed) => (
<div className="opacity-0 group-hover:opacity-100"> <div className="opacity-0 group-hover:opacity-100">

View File

@@ -24,7 +24,7 @@ import { useWorkspace } from "@app/context";
import { TFormSchema } from "./ProjectRoleModifySection.utils"; import { TFormSchema } from "./ProjectRoleModifySection.utils";
type Props = { type Props = {
formName: "secrets" | "folders" | "secret-imports"; formName: "secrets";
isNonEditable?: boolean; isNonEditable?: boolean;
setValue: UseFormSetValue<TFormSchema>; setValue: UseFormSetValue<TFormSchema>;
control: Control<TFormSchema>; control: Control<TFormSchema>;

View File

@@ -6,9 +6,7 @@ import {
faArrowLeft, faArrowLeft,
faBook, faBook,
faCog, faCog,
faFolder,
faKey, faKey,
faLink,
faLock, faLock,
faMagnifyingGlass, faMagnifyingGlass,
faNetworkWired, faNetworkWired,
@@ -244,32 +242,10 @@ export const ProjectRoleModifySection = ({ role, onGoBack }: Props) => {
setValue={setValue} setValue={setValue}
icon={faLock} icon={faLock}
title="Secrets" title="Secrets"
subtitle="Secret management control" subtitle="Create, modify and remove secrets, folders and secret imports"
formName="secrets" formName="secrets"
/> />
</div> </div>
<div>
<MultiEnvProjectPermission
isNonEditable={isNonEditable}
control={control}
setValue={setValue}
icon={faFolder}
title="Folders"
subtitle="Folder management control"
formName="folders"
/>
</div>
<div>
<MultiEnvProjectPermission
isNonEditable={isNonEditable}
control={control}
setValue={setValue}
icon={faLink}
title="Secret Imports"
subtitle="Secret import management control"
formName="secret-imports"
/>
</div>
<div key="permission-ws"> <div key="permission-ws">
<WsProjectPermission <WsProjectPermission
control={control} control={control}

View File

@@ -23,7 +23,6 @@ const multiEnvPermissionSchema = z
.optional(); .optional();
const PERMISSION_ACTIONS = ["read", "create", "edit", "delete"] as const; const PERMISSION_ACTIONS = ["read", "create", "edit", "delete"] as const;
const MULTI_ENV_KEY = ["secrets", "folders", "secret-imports"] as const;
export const formSchema = z.object({ export const formSchema = z.object({
name: z.string().trim(), name: z.string().trim(),
@@ -32,8 +31,6 @@ export const formSchema = z.object({
permissions: z permissions: z
.object({ .object({
secrets: z.record(multiEnvPermissionSchema).optional(), secrets: z.record(multiEnvPermissionSchema).optional(),
folders: z.record(multiEnvPermissionSchema).optional(),
"secret-imports": z.record(multiEnvPermissionSchema).optional(),
member: generalPermissionSchema, member: generalPermissionSchema,
role: generalPermissionSchema, role: generalPermissionSchema,
integrations: generalPermissionSchema, integrations: generalPermissionSchema,
@@ -92,7 +89,7 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
const { subject, action } = permission; const { subject, action } = permission;
if (!formVal?.[subject]) formVal[subject] = {}; if (!formVal?.[subject]) formVal[subject] = {};
if (["secrets", "folders", "secret-imports"].includes(subject)) { if (subject === "secrets") {
multiEnvApi2Form(formVal[subject], permission); multiEnvApi2Form(formVal[subject], permission);
} else { } else {
// everything else follows same pattern // everything else follows same pattern
@@ -107,7 +104,7 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
const multiEnvForm2Api = ( const multiEnvForm2Api = (
permissions: TProjectPermission[], permissions: TProjectPermission[],
formVal: Record<string, { secretPath?: string } & { [key: string]: boolean }>, formVal: Record<string, { secretPath?: string } & { [key: string]: boolean }>,
subject: (typeof MULTI_ENV_KEY)[number] subject: "secrets"
) => { ) => {
if (!formVal) return; if (!formVal) return;
@@ -147,12 +144,8 @@ export const formRolePermission2API = (formVal: TFormSchema["permissions"]) => {
// other than workspace everything else follows same // other than workspace everything else follows same
// if in future there is a different follow the above on how workspace is done // if in future there is a different follow the above on how workspace is done
Object.entries(formVal || {}).forEach(([rule, actions]) => { Object.entries(formVal || {}).forEach(([rule, actions]) => {
if (MULTI_ENV_KEY.includes(rule as (typeof MULTI_ENV_KEY)[number])) { if (rule === "secrets") {
multiEnvForm2Api( multiEnvForm2Api(permissions, JSON.parse(JSON.stringify(actions || {})), rule);
permissions,
JSON.parse(JSON.stringify(actions || {})),
rule as (typeof MULTI_ENV_KEY)[number]
);
} else { } else {
Object.entries(actions).forEach(([action, isAllowed]) => { Object.entries(actions).forEach(([action, isAllowed]) => {
if (isAllowed) { if (isAllowed) {

View File

@@ -2,7 +2,6 @@ import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { subject } from "@casl/ability";
import { import {
faArrowDown, faArrowDown,
faArrowUp, faArrowUp,
@@ -30,13 +29,7 @@ import {
Tooltip, Tooltip,
Tr Tr
} from "@app/components/v2"; } from "@app/components/v2";
import { import { useOrganization, useWorkspace } from "@app/context";
ProjectPermissionActions,
ProjectPermissionSub,
useOrganization,
useProjectPermission,
useWorkspace
} from "@app/context";
import { import {
useCreateSecretV3, useCreateSecretV3,
useDeleteSecretV3, useDeleteSecretV3,
@@ -82,7 +75,6 @@ export const SecretOverviewPage = () => {
const { data: latestFileKey } = useGetUserWsKey(workspaceId); const { data: latestFileKey } = useGetUserWsKey(workspaceId);
const [searchFilter, setSearchFilter] = useState(""); const [searchFilter, setSearchFilter] = useState("");
const secretPath = (router.query?.secretPath as string) || "/"; const secretPath = (router.query?.secretPath as string) || "/";
const permission = useProjectPermission();
useEffect(() => { useEffect(() => {
if (!isWorkspaceLoading && !workspaceId && router.isReady) { if (!isWorkspaceLoading && !workspaceId && router.isReady) {
@@ -90,22 +82,7 @@ export const SecretOverviewPage = () => {
} }
}, [isWorkspaceLoading, workspaceId, router.isReady]); }, [isWorkspaceLoading, workspaceId, router.isReady]);
const userAvailableEnvs = const userAvailableEnvs = currentWorkspace?.environments || [];
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 { const {
data: secrets, data: secrets,