mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge pull request #972 from akhilmhdh/feat/permission-patch
feat(rbac): grouped folder and imports permission into secret permission
This commit is contained in:
@@ -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 });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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: [] });
|
||||||
|
|||||||
@@ -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"]);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 }) => {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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>;
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user