mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-03 03:25:48 +00:00
fix(view-secret-value): backwards compatibility for read
This commit is contained in:
-313
@@ -1,313 +0,0 @@
|
|||||||
import { MongoAbility, RawRuleOf } from "@casl/ability";
|
|
||||||
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
|
||||||
import { Knex } from "knex";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { selectAllTableCols } from "@app/lib/knex";
|
|
||||||
|
|
||||||
import { TableName } from "../schemas";
|
|
||||||
|
|
||||||
enum ProjectPermissionSub {
|
|
||||||
Secrets = "secrets"
|
|
||||||
}
|
|
||||||
|
|
||||||
enum SecretActions {
|
|
||||||
Read = "read",
|
|
||||||
ReadValue = "readValue"
|
|
||||||
}
|
|
||||||
|
|
||||||
const UnpackedPermissionSchema = z.object({
|
|
||||||
subject: z
|
|
||||||
.union([z.string().min(1), z.string().array()])
|
|
||||||
.transform((el) => (typeof el !== "string" ? el[0] : el))
|
|
||||||
.optional(),
|
|
||||||
action: z.union([z.string().min(1), z.string().array()]).transform((el) => (typeof el === "string" ? [el] : el)),
|
|
||||||
conditions: z.unknown().optional(),
|
|
||||||
inverted: z.boolean().optional()
|
|
||||||
});
|
|
||||||
|
|
||||||
const $unpackPermissions = (permissions: unknown) =>
|
|
||||||
UnpackedPermissionSchema.array().parse(unpackRules((permissions || []) as PackRule<RawRuleOf<MongoAbility>>[]));
|
|
||||||
|
|
||||||
const $updatePermissionsUp = (permissions: unknown) => {
|
|
||||||
const parsedPermissions = $unpackPermissions(permissions);
|
|
||||||
let shouldUpdate = false;
|
|
||||||
|
|
||||||
for (let i = 0; i < parsedPermissions.length; i += 1) {
|
|
||||||
const parsedPermission = parsedPermissions[i];
|
|
||||||
const { subject, action } = parsedPermission;
|
|
||||||
|
|
||||||
if (subject === ProjectPermissionSub.Secrets) {
|
|
||||||
if (action.includes(SecretActions.Read) && !action.includes(SecretActions.ReadValue)) {
|
|
||||||
action.push(SecretActions.ReadValue);
|
|
||||||
parsedPermissions[i] = { ...parsedPermission, action };
|
|
||||||
shouldUpdate = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
parsedPermissions,
|
|
||||||
shouldUpdate
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const $updatePermissionsDown = (permissions: unknown) => {
|
|
||||||
const parsedPermissions = $unpackPermissions(permissions);
|
|
||||||
|
|
||||||
let shouldUpdate = false;
|
|
||||||
for (let i = 0; i < parsedPermissions.length; i += 1) {
|
|
||||||
const parsedPermission = parsedPermissions[i];
|
|
||||||
|
|
||||||
const { subject, action } = parsedPermission;
|
|
||||||
|
|
||||||
if (subject === ProjectPermissionSub.Secrets) {
|
|
||||||
const readValueIndex = action.indexOf(SecretActions.ReadValue);
|
|
||||||
|
|
||||||
if (action.includes(SecretActions.ReadValue) && readValueIndex !== -1) {
|
|
||||||
action.splice(readValueIndex, 1);
|
|
||||||
parsedPermissions[i] = { ...parsedPermission, action };
|
|
||||||
|
|
||||||
shouldUpdate = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const repackedPermissions = packRules(parsedPermissions);
|
|
||||||
|
|
||||||
return {
|
|
||||||
repackedPermissions,
|
|
||||||
shouldUpdate
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const CHUNK_SIZE = 1000;
|
|
||||||
|
|
||||||
export async function up(knex: Knex): Promise<void> {
|
|
||||||
const projectRoles = await knex(TableName.ProjectRoles).select(selectAllTableCols(TableName.ProjectRoles));
|
|
||||||
const projectIdentityAdditionalPrivileges = await knex(TableName.IdentityProjectAdditionalPrivilege).select(
|
|
||||||
selectAllTableCols(TableName.IdentityProjectAdditionalPrivilege)
|
|
||||||
);
|
|
||||||
const projectUserAdditionalPrivileges = await knex(TableName.ProjectUserAdditionalPrivilege).select(
|
|
||||||
selectAllTableCols(TableName.ProjectUserAdditionalPrivilege)
|
|
||||||
);
|
|
||||||
|
|
||||||
const serviceTokens = await knex(TableName.ServiceToken).select(selectAllTableCols(TableName.ServiceToken));
|
|
||||||
|
|
||||||
const updatedServiceTokens = serviceTokens.reduce<typeof serviceTokens>((acc, serviceToken) => {
|
|
||||||
const { permissions } = serviceToken; // Service tokens are special, and include an array of actions only.
|
|
||||||
|
|
||||||
if (permissions.includes(SecretActions.Read) && !permissions.includes(SecretActions.ReadValue)) {
|
|
||||||
permissions.push(SecretActions.ReadValue);
|
|
||||||
acc.push({
|
|
||||||
...serviceToken,
|
|
||||||
permissions
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (updatedServiceTokens.length > 0) {
|
|
||||||
for (let i = 0; i < updatedServiceTokens.length; i += CHUNK_SIZE) {
|
|
||||||
const chunk = updatedServiceTokens.slice(i, i + CHUNK_SIZE);
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await knex(TableName.ServiceToken)
|
|
||||||
.whereIn(
|
|
||||||
"id",
|
|
||||||
chunk.map((t) => t.id)
|
|
||||||
)
|
|
||||||
.update({
|
|
||||||
// @ts-expect-error -- raw query
|
|
||||||
permissions: knex.raw(
|
|
||||||
`CASE id
|
|
||||||
${chunk.map((t) => `WHEN '${t.id}' THEN ?::text[]`).join(" ")}
|
|
||||||
END`,
|
|
||||||
chunk.map((t) => t.permissions)
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedRoles = projectRoles.reduce<typeof projectRoles>((acc, projectRole) => {
|
|
||||||
const { shouldUpdate, parsedPermissions } = $updatePermissionsUp(projectRole.permissions);
|
|
||||||
|
|
||||||
if (shouldUpdate) {
|
|
||||||
acc.push({
|
|
||||||
...projectRole,
|
|
||||||
permissions: JSON.stringify(packRules(parsedPermissions))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const updatedIdentityAdditionalPrivileges = projectIdentityAdditionalPrivileges.reduce<
|
|
||||||
typeof projectIdentityAdditionalPrivileges
|
|
||||||
>((acc, identityAdditionalPrivilege) => {
|
|
||||||
const { shouldUpdate, parsedPermissions } = $updatePermissionsUp(identityAdditionalPrivilege.permissions);
|
|
||||||
|
|
||||||
if (shouldUpdate) {
|
|
||||||
acc.push({
|
|
||||||
...identityAdditionalPrivilege,
|
|
||||||
permissions: JSON.stringify(packRules(parsedPermissions))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const updatedUserAdditionalPrivileges = projectUserAdditionalPrivileges.reduce<
|
|
||||||
typeof projectUserAdditionalPrivileges
|
|
||||||
>((acc, userAdditionalPrivilege) => {
|
|
||||||
const { shouldUpdate, parsedPermissions } = $updatePermissionsUp(userAdditionalPrivilege.permissions);
|
|
||||||
|
|
||||||
if (shouldUpdate) {
|
|
||||||
acc.push({
|
|
||||||
...userAdditionalPrivilege,
|
|
||||||
permissions: JSON.stringify(packRules(parsedPermissions))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (updatedRoles.length > 0) {
|
|
||||||
for (let i = 0; i < updatedRoles.length; i += CHUNK_SIZE) {
|
|
||||||
const chunk = updatedRoles.slice(i, i + CHUNK_SIZE);
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await knex(TableName.ProjectRoles).insert(chunk).onConflict("id").merge(["permissions"]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updatedIdentityAdditionalPrivileges.length > 0) {
|
|
||||||
for (let i = 0; i < updatedIdentityAdditionalPrivileges.length; i += CHUNK_SIZE) {
|
|
||||||
const chunk = updatedIdentityAdditionalPrivileges.slice(i, i + CHUNK_SIZE);
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await knex(TableName.IdentityProjectAdditionalPrivilege).insert(chunk).onConflict("id").merge(["permissions"]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updatedUserAdditionalPrivileges.length > 0) {
|
|
||||||
for (let i = 0; i < updatedUserAdditionalPrivileges.length; i += CHUNK_SIZE) {
|
|
||||||
const chunk = updatedUserAdditionalPrivileges.slice(i, i + CHUNK_SIZE);
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await knex(TableName.ProjectUserAdditionalPrivilege).insert(chunk).onConflict("id").merge(["permissions"]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
|
||||||
const projectRoles = await knex(TableName.ProjectRoles).select(selectAllTableCols(TableName.ProjectRoles));
|
|
||||||
const identityAdditionalPrivileges = await knex(TableName.IdentityProjectAdditionalPrivilege).select(
|
|
||||||
selectAllTableCols(TableName.IdentityProjectAdditionalPrivilege)
|
|
||||||
);
|
|
||||||
const userAdditionalPrivileges = await knex(TableName.ProjectUserAdditionalPrivilege).select(
|
|
||||||
selectAllTableCols(TableName.ProjectUserAdditionalPrivilege)
|
|
||||||
);
|
|
||||||
const serviceTokens = await knex(TableName.ServiceToken).select(selectAllTableCols(TableName.ServiceToken));
|
|
||||||
|
|
||||||
const updatedServiceTokens = serviceTokens.reduce<typeof serviceTokens>((acc, serviceToken) => {
|
|
||||||
const { permissions } = serviceToken;
|
|
||||||
|
|
||||||
if (permissions.includes(SecretActions.ReadValue)) {
|
|
||||||
permissions.splice(permissions.indexOf(SecretActions.ReadValue), 1);
|
|
||||||
acc.push({
|
|
||||||
...serviceToken,
|
|
||||||
permissions
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (updatedServiceTokens.length > 0) {
|
|
||||||
for (let i = 0; i < updatedServiceTokens.length; i += CHUNK_SIZE) {
|
|
||||||
const chunk = updatedServiceTokens.slice(i, i + CHUNK_SIZE);
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await knex(TableName.ServiceToken)
|
|
||||||
.whereIn(
|
|
||||||
"id",
|
|
||||||
chunk.map((t) => t.id)
|
|
||||||
)
|
|
||||||
.update({
|
|
||||||
// @ts-expect-error -- raw query
|
|
||||||
permissions: knex.raw(
|
|
||||||
`CASE id
|
|
||||||
${chunk.map((t) => `WHEN '${t.id}' THEN ?::text[]`).join(" ")}
|
|
||||||
END`,
|
|
||||||
chunk.map((t) => t.permissions)
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedRoles = projectRoles.reduce<typeof projectRoles>((acc, projectRole) => {
|
|
||||||
const { shouldUpdate, repackedPermissions } = $updatePermissionsDown(projectRole.permissions);
|
|
||||||
|
|
||||||
if (shouldUpdate) {
|
|
||||||
acc.push({
|
|
||||||
...projectRole,
|
|
||||||
permissions: JSON.stringify(repackedPermissions)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const updatedIdentityAdditionalPrivileges = identityAdditionalPrivileges.reduce<typeof identityAdditionalPrivileges>(
|
|
||||||
(acc, identityAdditionalPrivilege) => {
|
|
||||||
const { shouldUpdate, repackedPermissions } = $updatePermissionsDown(identityAdditionalPrivilege.permissions);
|
|
||||||
|
|
||||||
if (shouldUpdate) {
|
|
||||||
acc.push({
|
|
||||||
...identityAdditionalPrivilege,
|
|
||||||
permissions: JSON.stringify(repackedPermissions)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const updatedUserAdditionalPrivileges = userAdditionalPrivileges.reduce<typeof userAdditionalPrivileges>(
|
|
||||||
(acc, userAdditionalPrivilege) => {
|
|
||||||
const { shouldUpdate, repackedPermissions } = $updatePermissionsDown(userAdditionalPrivilege.permissions);
|
|
||||||
|
|
||||||
if (shouldUpdate) {
|
|
||||||
acc.push({
|
|
||||||
...userAdditionalPrivilege,
|
|
||||||
permissions: JSON.stringify(repackedPermissions)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (updatedRoles.length > 0) {
|
|
||||||
for (let i = 0; i < updatedRoles.length; i += CHUNK_SIZE) {
|
|
||||||
const chunk = updatedRoles.slice(i, i + CHUNK_SIZE);
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await knex(TableName.ProjectRoles).insert(chunk).onConflict("id").merge(["permissions"]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updatedIdentityAdditionalPrivileges.length > 0) {
|
|
||||||
for (let i = 0; i < updatedIdentityAdditionalPrivileges.length; i += CHUNK_SIZE) {
|
|
||||||
const chunk = updatedIdentityAdditionalPrivileges.slice(i, i + CHUNK_SIZE);
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await knex(TableName.IdentityProjectAdditionalPrivilege).insert(chunk).onConflict("id").merge(["permissions"]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updatedUserAdditionalPrivileges.length > 0) {
|
|
||||||
for (let i = 0; i < updatedUserAdditionalPrivileges.length; i += CHUNK_SIZE) {
|
|
||||||
const chunk = updatedUserAdditionalPrivileges.slice(i, i + CHUNK_SIZE);
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await knex(TableName.ProjectUserAdditionalPrivilege).insert(chunk).onConflict("id").merge(["permissions"]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,7 @@ import { packRules } from "@casl/ability/extra";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ProjectMembershipRole, ProjectRolesSchema } from "@app/db/schemas";
|
import { ProjectMembershipRole, ProjectRolesSchema } from "@app/db/schemas";
|
||||||
|
import { checkForInvalidPermissionCombination } from "@app/ee/services/permission/permission-fns";
|
||||||
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
|
||||||
import { PROJECT_ROLE } from "@app/lib/api-docs";
|
import { PROJECT_ROLE } from "@app/lib/api-docs";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
@@ -92,7 +93,10 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
.describe(PROJECT_ROLE.UPDATE.slug),
|
.describe(PROJECT_ROLE.UPDATE.slug),
|
||||||
name: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.name),
|
name: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.name),
|
||||||
description: z.string().trim().nullish().describe(PROJECT_ROLE.UPDATE.description),
|
description: z.string().trim().nullish().describe(PROJECT_ROLE.UPDATE.description),
|
||||||
permissions: ProjectPermissionV2Schema.array().describe(PROJECT_ROLE.UPDATE.permissions).optional()
|
permissions: ProjectPermissionV2Schema.array()
|
||||||
|
.describe(PROJECT_ROLE.UPDATE.permissions)
|
||||||
|
.optional()
|
||||||
|
.superRefine(checkForInvalidPermissionCombination)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
|||||||
@@ -1,7 +1,108 @@
|
|||||||
|
/* eslint-disable no-nested-ternary */
|
||||||
|
import { ForbiddenError, MongoAbility, subject } from "@casl/ability";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
import { TOrganizations } from "@app/db/schemas";
|
import { TOrganizations } from "@app/db/schemas";
|
||||||
import { ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||||
import { ActorAuthMethod, AuthMethod } from "@app/services/auth/auth-type";
|
import { ActorAuthMethod, AuthMethod } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
import {
|
||||||
|
ProjectPermissionSecretActions,
|
||||||
|
ProjectPermissionSet,
|
||||||
|
ProjectPermissionSub,
|
||||||
|
ProjectPermissionV2Schema,
|
||||||
|
SecretSubjectFields
|
||||||
|
} from "./project-permission";
|
||||||
|
|
||||||
|
export function CheckForbiddenErrorSecretsSubject(
|
||||||
|
permission: MongoAbility<ProjectPermissionSet>,
|
||||||
|
action: Extract<
|
||||||
|
ProjectPermissionSecretActions,
|
||||||
|
ProjectPermissionSecretActions.ReadValue | ProjectPermissionSecretActions.DescribeSecret
|
||||||
|
>,
|
||||||
|
subjectFields?: SecretSubjectFields
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (subjectFields) {
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(action, subject(ProjectPermissionSub.Secrets, subjectFields));
|
||||||
|
} else {
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(action, ProjectPermissionSub.Secrets);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
if (subjectFields) {
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionSecretActions.DescribeAndReadValue,
|
||||||
|
subject(ProjectPermissionSub.Secrets, subjectFields)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionSecretActions.DescribeAndReadValue,
|
||||||
|
ProjectPermissionSub.Secrets
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CheckCanSecretsSubject(
|
||||||
|
permission: MongoAbility<ProjectPermissionSet>,
|
||||||
|
action: Extract<
|
||||||
|
ProjectPermissionSecretActions,
|
||||||
|
ProjectPermissionSecretActions.DescribeSecret | ProjectPermissionSecretActions.ReadValue
|
||||||
|
>,
|
||||||
|
subjectFields?: SecretSubjectFields
|
||||||
|
) {
|
||||||
|
let canNewPermission = false;
|
||||||
|
let canOldPermission = false;
|
||||||
|
|
||||||
|
if (subjectFields) {
|
||||||
|
canNewPermission = permission.can(action, subject(ProjectPermissionSub.Secrets, subjectFields));
|
||||||
|
canOldPermission = permission.can(
|
||||||
|
ProjectPermissionSecretActions.DescribeAndReadValue,
|
||||||
|
subject(ProjectPermissionSub.Secrets, subjectFields)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
canNewPermission = permission.can(action, ProjectPermissionSub.Secrets);
|
||||||
|
canOldPermission = permission.can(
|
||||||
|
ProjectPermissionSecretActions.DescribeAndReadValue,
|
||||||
|
ProjectPermissionSub.Secrets
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return canNewPermission || canOldPermission;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OptionalArrayPermissionSchema = ProjectPermissionV2Schema.array().optional();
|
||||||
|
export function checkForInvalidPermissionCombination(permissions: z.infer<typeof OptionalArrayPermissionSchema>) {
|
||||||
|
if (!permissions) return;
|
||||||
|
|
||||||
|
for (const permission of permissions) {
|
||||||
|
if (permission.subject === ProjectPermissionSub.Secrets) {
|
||||||
|
if (permission.action.includes(ProjectPermissionSecretActions.DescribeAndReadValue)) {
|
||||||
|
const hasReadValue = permission.action.includes(ProjectPermissionSecretActions.ReadValue);
|
||||||
|
const hasDescribeSecret = permission.action.includes(ProjectPermissionSecretActions.DescribeSecret);
|
||||||
|
|
||||||
|
if (!hasReadValue && !hasDescribeSecret) return;
|
||||||
|
|
||||||
|
const hasBothDescribeAndReadValue =
|
||||||
|
permission.action.includes(ProjectPermissionSecretActions.DescribeSecret) &&
|
||||||
|
permission.action.includes(ProjectPermissionSecretActions.ReadValue);
|
||||||
|
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `You have selected Full Read Access, and ${
|
||||||
|
hasBothDescribeAndReadValue
|
||||||
|
? "both Read Value and Describe Secret"
|
||||||
|
: hasReadValue
|
||||||
|
? "Read Value"
|
||||||
|
: hasDescribeSecret
|
||||||
|
? "Describe Secret"
|
||||||
|
: ""
|
||||||
|
}. You cannot select Read Value or Describe Secret if you have selected Full Read Access.`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function isAuthMethodSaml(actorAuthMethod: ActorAuthMethod) {
|
function isAuthMethodSaml(actorAuthMethod: ActorAuthMethod) {
|
||||||
if (!actorAuthMethod) return false;
|
if (!actorAuthMethod) return false;
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ export enum ProjectPermissionActions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionSecretActions {
|
export enum ProjectPermissionSecretActions {
|
||||||
DescribeSecret = "read",
|
DescribeAndReadValue = "read",
|
||||||
|
DescribeSecret = "describeSecret",
|
||||||
ReadValue = "readValue",
|
ReadValue = "readValue",
|
||||||
Create = "create",
|
Create = "create",
|
||||||
Edit = "edit",
|
Edit = "edit",
|
||||||
@@ -564,6 +565,7 @@ const buildAdminPermissionRules = () => {
|
|||||||
|
|
||||||
can(
|
can(
|
||||||
[
|
[
|
||||||
|
// not adding DescribeAndReadValue, because it's already covered by DescribeSecret and ReadValue
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
ProjectPermissionSecretActions.DescribeSecret,
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
ProjectPermissionSecretActions.Create,
|
ProjectPermissionSecretActions.Create,
|
||||||
@@ -632,6 +634,7 @@ const buildMemberPermissionRules = () => {
|
|||||||
|
|
||||||
can(
|
can(
|
||||||
[
|
[
|
||||||
|
// not adding DescribeAndReadValue, because it's already covered by DescribeSecret and ReadValue
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
ProjectPermissionSecretActions.DescribeSecret,
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
ProjectPermissionSecretActions.Edit,
|
ProjectPermissionSecretActions.Edit,
|
||||||
@@ -808,6 +811,7 @@ export const projectMemberPermissions = buildMemberPermissionRules();
|
|||||||
const buildViewerPermissionRules = () => {
|
const buildViewerPermissionRules = () => {
|
||||||
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||||
|
|
||||||
|
// not adding DescribeAndReadValue, because it's already covered by DescribeSecret and ReadValue
|
||||||
can(ProjectPermissionSecretActions.DescribeSecret, ProjectPermissionSub.Secrets);
|
can(ProjectPermissionSecretActions.DescribeSecret, ProjectPermissionSub.Secrets);
|
||||||
can(ProjectPermissionSecretActions.ReadValue, ProjectPermissionSub.Secrets);
|
can(ProjectPermissionSecretActions.ReadValue, ProjectPermissionSub.Secrets);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretFolders);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretFolders);
|
||||||
@@ -852,7 +856,6 @@ export const buildServiceTokenProjectPermission = (
|
|||||||
) => {
|
) => {
|
||||||
const canWrite = permission.includes("write");
|
const canWrite = permission.includes("write");
|
||||||
const canRead = permission.includes("read");
|
const canRead = permission.includes("read");
|
||||||
const canReadValue = permission.includes("readValue");
|
|
||||||
|
|
||||||
const { can, build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
const { can, build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||||
scopes.forEach(({ secretPath, environment }) => {
|
scopes.forEach(({ secretPath, environment }) => {
|
||||||
@@ -876,7 +879,7 @@ export const buildServiceTokenProjectPermission = (
|
|||||||
environment
|
environment
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (canRead) {
|
if (canRead && subject !== ProjectPermissionSub.Secrets) {
|
||||||
can(ProjectPermissionActions.Read, subject, {
|
can(ProjectPermissionActions.Read, subject, {
|
||||||
// @ts-expect-error type
|
// @ts-expect-error type
|
||||||
secretPath: { $glob: secretPath },
|
secretPath: { $glob: secretPath },
|
||||||
@@ -884,12 +887,18 @@ export const buildServiceTokenProjectPermission = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subject === ProjectPermissionSub.Secrets && canReadValue) {
|
if (subject === ProjectPermissionSub.Secrets && canRead) {
|
||||||
// @ts-expect-error type
|
// @ts-expect-error type
|
||||||
can(ProjectPermissionSecretActions.ReadValue, subject as ProjectPermissionSub.Secrets, {
|
can(ProjectPermissionSecretActions.ReadValue, subject as ProjectPermissionSub.Secrets, {
|
||||||
secretPath: { $glob: secretPath },
|
secretPath: { $glob: secretPath },
|
||||||
environment
|
environment
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// @ts-expect-error type
|
||||||
|
can(ProjectPermissionSecretActions.DescribeSecret, subject as ProjectPermissionSub.Secrets, {
|
||||||
|
secretPath: { $glob: secretPath },
|
||||||
|
environment
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ import {
|
|||||||
TSecretApprovalDetailsDTO,
|
TSecretApprovalDetailsDTO,
|
||||||
TStatusChangeDTO
|
TStatusChangeDTO
|
||||||
} from "./secret-approval-request-types";
|
} from "./secret-approval-request-types";
|
||||||
|
import { CheckForbiddenErrorSecretsSubject } from "../permission/permission-fns";
|
||||||
|
|
||||||
type TSecretApprovalRequestServiceFactoryDep = {
|
type TSecretApprovalRequestServiceFactoryDep = {
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
@@ -917,10 +918,11 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
environment,
|
||||||
);
|
secretPath
|
||||||
|
});
|
||||||
|
|
||||||
await projectDAL.checkProjectUpgradeStatus(projectId);
|
await projectDAL.checkProjectUpgradeStatus(projectId);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-argument */
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-argument */
|
||||||
// akhilmhdh: I did this, quite strange bug with eslint. Everything do have a type stil has this error
|
// akhilmhdh: I did this, quite strange bug with eslint. Everything do have a type stil has this error
|
||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
import { ActionProjectType, TableName, TSecretTagJunctionInsert, TSecretV2TagJunctionInsert } from "@app/db/schemas";
|
import { ActionProjectType, TableName, TSecretTagJunctionInsert, TSecretV2TagJunctionInsert } from "@app/db/schemas";
|
||||||
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
||||||
@@ -12,6 +12,7 @@ import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
|||||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||||
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
||||||
import { TSecretDALFactory } from "@app/services/secret/secret-dal";
|
import { TSecretDALFactory } from "@app/services/secret/secret-dal";
|
||||||
|
import { INFISICAL_SECRET_VALUE_HIDDEN_MASK } from "@app/services/secret/secret-fns";
|
||||||
import { TSecretVersionDALFactory } from "@app/services/secret/secret-version-dal";
|
import { TSecretVersionDALFactory } from "@app/services/secret/secret-version-dal";
|
||||||
import { TSecretVersionTagDALFactory } from "@app/services/secret/secret-version-tag-dal";
|
import { TSecretVersionTagDALFactory } from "@app/services/secret/secret-version-tag-dal";
|
||||||
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
||||||
@@ -22,6 +23,7 @@ import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secre
|
|||||||
import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal";
|
import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal";
|
||||||
|
|
||||||
import { TLicenseServiceFactory } from "../license/license-service";
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
|
import { CheckCanSecretsSubject, CheckForbiddenErrorSecretsSubject } from "../permission/permission-fns";
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
@@ -39,7 +41,6 @@ import { TSnapshotFolderDALFactory } from "./snapshot-folder-dal";
|
|||||||
import { TSnapshotSecretDALFactory } from "./snapshot-secret-dal";
|
import { TSnapshotSecretDALFactory } from "./snapshot-secret-dal";
|
||||||
import { TSnapshotSecretV2DALFactory } from "./snapshot-secret-v2-dal";
|
import { TSnapshotSecretV2DALFactory } from "./snapshot-secret-v2-dal";
|
||||||
import { getFullFolderPath } from "./snapshot-service-fns";
|
import { getFullFolderPath } from "./snapshot-service-fns";
|
||||||
import { INFISICAL_SECRET_VALUE_HIDDEN_MASK } from "@app/services/secret/secret-fns";
|
|
||||||
|
|
||||||
type TSecretSnapshotServiceFactoryDep = {
|
type TSecretSnapshotServiceFactoryDep = {
|
||||||
snapshotDAL: TSnapshotDALFactory;
|
snapshotDAL: TSnapshotDALFactory;
|
||||||
@@ -102,10 +103,10 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
||||||
|
|
||||||
// We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder.
|
// We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder.
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.DescribeSecret, {
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
environment,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
secretPath: path
|
||||||
);
|
});
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||||
if (!folder) {
|
if (!folder) {
|
||||||
@@ -139,10 +140,10 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
||||||
|
|
||||||
// We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder.
|
// We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder.
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.DescribeSecret, {
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
environment,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
secretPath: path
|
||||||
);
|
});
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||||
if (!folder)
|
if (!folder)
|
||||||
@@ -186,15 +187,12 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
snapshotDetails = {
|
snapshotDetails = {
|
||||||
...encryptedSnapshotDetails,
|
...encryptedSnapshotDetails,
|
||||||
secretVersions: encryptedSnapshotDetails.secretVersions.map((el) => {
|
secretVersions: encryptedSnapshotDetails.secretVersions.map((el) => {
|
||||||
const canReadValue = permission.can(
|
const canReadValue = CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment: encryptedSnapshotDetails.environment.slug,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath: fullFolderPath,
|
||||||
environment: encryptedSnapshotDetails.environment.slug,
|
secretName: el.key,
|
||||||
secretPath: fullFolderPath,
|
secretTags: el.tags.length ? el.tags.map((tag) => tag.slug) : undefined
|
||||||
secretName: el.key,
|
});
|
||||||
secretTags: el.tags.length ? el.tags.map((tag) => tag.slug) : undefined
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
let secretValue = "";
|
let secretValue = "";
|
||||||
if (canReadValue) {
|
if (canReadValue) {
|
||||||
@@ -238,15 +236,12 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
key: botKey
|
key: botKey
|
||||||
});
|
});
|
||||||
|
|
||||||
const canReadValue = permission.can(
|
const canReadValue = CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment: encryptedSnapshotDetails.environment.slug,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath: fullFolderPath,
|
||||||
environment: encryptedSnapshotDetails.environment.slug,
|
secretName: secretKey,
|
||||||
secretPath: fullFolderPath,
|
secretTags: el.tags.length ? el.tags.map((tag) => tag.slug) : undefined
|
||||||
secretName: secretKey,
|
});
|
||||||
secretTags: el.tags.length ? el.tags.map((tag) => tag.slug) : undefined
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
let secretValue = "";
|
let secretValue = "";
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ export const registerServiceTokenRouter = async (server: FastifyZodProvider) =>
|
|||||||
iv: z.string().trim(),
|
iv: z.string().trim(),
|
||||||
tag: z.string().trim(),
|
tag: z.string().trim(),
|
||||||
expiresIn: z.number().nullable(),
|
expiresIn: z.number().nullable(),
|
||||||
permissions: z.enum(["read", "write", "readValue"]).array()
|
permissions: z.enum(["read", "write"]).array()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
import { ActionProjectType } from "@app/db/schemas";
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
|
import { CheckForbiddenErrorSecretsSubject } from "@app/ee/services/permission/permission-fns";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
@@ -95,13 +96,10 @@ export const integrationServiceFactory = ({
|
|||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment: sourceEnvironment,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath
|
||||||
environment: sourceEnvironment,
|
});
|
||||||
secretPath
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(integrationAuth.projectId, sourceEnvironment, secretPath);
|
const folder = await folderDAL.findBySecretPath(integrationAuth.projectId, sourceEnvironment, secretPath);
|
||||||
if (!folder) {
|
if (!folder) {
|
||||||
@@ -178,13 +176,10 @@ export const integrationServiceFactory = ({
|
|||||||
const newSecretPath = secretPath || integration.secretPath;
|
const newSecretPath = secretPath || integration.secretPath;
|
||||||
|
|
||||||
if (environment || secretPath) {
|
if (environment || secretPath) {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment: newEnvironment,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath: newSecretPath
|
||||||
environment: newEnvironment,
|
});
|
||||||
secretPath: newSecretPath
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(integration.projectId, newEnvironment, newSecretPath);
|
const folder = await folderDAL.findBySecretPath(integration.projectId, newEnvironment, newSecretPath);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||||
|
import { CheckForbiddenErrorSecretsSubject } from "@app/ee/services/permission/permission-fns";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
@@ -764,10 +765,7 @@ export const projectServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actionProjectType: ActionProjectType.Any
|
actionProjectType: ActionProjectType.Any
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.DescribeSecret);
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
|
||||||
ProjectPermissionSub.Secrets
|
|
||||||
);
|
|
||||||
|
|
||||||
const project = await projectDAL.findProjectById(projectId);
|
const project = await projectDAL.findProjectById(projectId);
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { ForbiddenError, subject } from "@casl/ability";
|
|||||||
|
|
||||||
import { ActionProjectType, TableName } from "@app/db/schemas";
|
import { ActionProjectType, TableName } from "@app/db/schemas";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
|
import { CheckCanSecretsSubject, CheckForbiddenErrorSecretsSubject } from "@app/ee/services/permission/permission-fns";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
@@ -93,13 +94,11 @@ export const secretImportServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
// check if user has permission to import from target path
|
// check if user has permission to import from target path
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.DescribeSecret, {
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
environment: data.environment,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath: data.path
|
||||||
environment: data.environment,
|
});
|
||||||
secretPath: data.path
|
|
||||||
})
|
|
||||||
);
|
|
||||||
if (isReplication) {
|
if (isReplication) {
|
||||||
const plan = await licenseService.getPlan(actorOrgId);
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
if (!plan.secretApproval) {
|
if (!plan.secretApproval) {
|
||||||
@@ -405,13 +404,10 @@ export const secretImportServiceFactory = ({
|
|||||||
if (!secretImportDoc.isReplication) throw new BadRequestError({ message: "Import is not in replication mode" });
|
if (!secretImportDoc.isReplication) throw new BadRequestError({ message: "Import is not in replication mode" });
|
||||||
|
|
||||||
// check if user has permission to import from target path
|
// check if user has permission to import from target path
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.DescribeSecret, {
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
environment: secretImportDoc.importEnv.slug,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath: secretImportDoc.importPath
|
||||||
environment: secretImportDoc.importEnv.slug,
|
});
|
||||||
secretPath: secretImportDoc.importPath
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
await projectDAL.checkProjectUpgradeStatus(projectId);
|
await projectDAL.checkProjectUpgradeStatus(projectId);
|
||||||
|
|
||||||
@@ -599,14 +595,12 @@ export const secretImportServiceFactory = ({
|
|||||||
// so anything based on this order will also be in right position
|
// so anything based on this order will also be in right position
|
||||||
const secretImports = await secretImportDAL.find({ folderId: folder.id, isReplication: false });
|
const secretImports = await secretImportDAL.find({ folderId: folder.id, isReplication: false });
|
||||||
const allowedImports = secretImports.filter((el) =>
|
const allowedImports = secretImports.filter((el) =>
|
||||||
permission.can(
|
CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment: el.importEnv.slug,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath: el.importPath
|
||||||
environment: el.importEnv.slug,
|
})
|
||||||
secretPath: el.importPath
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return fnSecretsFromImports({ allowedImports, folderDAL, secretDAL, secretImportDAL });
|
return fnSecretsFromImports({ allowedImports, folderDAL, secretDAL, secretImportDAL });
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -651,16 +645,14 @@ export const secretImportServiceFactory = ({
|
|||||||
secretImportDAL,
|
secretImportDAL,
|
||||||
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""),
|
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""),
|
||||||
hasSecretAccess: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) =>
|
hasSecretAccess: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) =>
|
||||||
permission.can(
|
CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment: expandEnvironment,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath: expandSecretPath,
|
||||||
environment: expandEnvironment,
|
secretName: expandSecretKey,
|
||||||
secretPath: expandSecretPath,
|
secretTags: expandSecretTags
|
||||||
secretName: expandSecretKey,
|
})
|
||||||
secretTags: expandSecretTags
|
|
||||||
})
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return importedSecrets;
|
return importedSecrets;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -671,13 +663,10 @@ export const secretImportServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const allowedImports = secretImports.filter((el) =>
|
const allowedImports = secretImports.filter((el) =>
|
||||||
permission.can(
|
CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment: el.importEnv.slug,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath: el.importPath
|
||||||
environment: el.importEnv.slug,
|
})
|
||||||
secretPath: el.importPath
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
const importedSecrets = await fnSecretsFromImports({
|
const importedSecrets = await fnSecretsFromImports({
|
||||||
allowedImports,
|
allowedImports,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
import { ActionProjectType } from "@app/db/schemas";
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
|
import { CheckForbiddenErrorSecretsSubject } from "@app/ee/services/permission/permission-fns";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionSecretActions,
|
ProjectPermissionSecretActions,
|
||||||
@@ -178,13 +179,10 @@ export const secretSyncServiceFactory = ({
|
|||||||
ProjectPermissionSub.SecretSyncs
|
ProjectPermissionSub.SecretSyncs
|
||||||
);
|
);
|
||||||
|
|
||||||
ForbiddenError.from(projectPermission).throwUnlessCan(
|
CheckForbiddenErrorSecretsSubject(projectPermission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath
|
||||||
environment,
|
});
|
||||||
secretPath
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
||||||
|
|
||||||
@@ -269,13 +267,10 @@ export const secretSyncServiceFactory = ({
|
|||||||
if (!updatedEnvironment || !updatedSecretPath)
|
if (!updatedEnvironment || !updatedSecretPath)
|
||||||
throw new BadRequestError({ message: "Must specify both source environment and secret path" });
|
throw new BadRequestError({ message: "Must specify both source environment and secret path" });
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment: updatedEnvironment,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath: updatedSecretPath
|
||||||
environment: updatedEnvironment,
|
});
|
||||||
secretPath: updatedSecretPath
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const newFolder = await folderDAL.findBySecretPath(secretSync.projectId, updatedEnvironment, updatedSecretPath);
|
const newFolder = await folderDAL.findBySecretPath(secretSync.projectId, updatedEnvironment, updatedSecretPath);
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
TableName,
|
TableName,
|
||||||
TSecretsV2
|
TSecretsV2
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
|
import { CheckCanSecretsSubject, CheckForbiddenErrorSecretsSubject } from "@app/ee/services/permission/permission-fns";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
@@ -540,17 +541,14 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const secretValueHidden = !permission.can(
|
const secretValueHidden = !CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath,
|
||||||
environment,
|
secretName: inputSecret.secretName,
|
||||||
secretPath,
|
...(tagsToCheck.length && {
|
||||||
secretName: inputSecret.secretName,
|
secretTags: tagsToCheck.map((el) => el.slug)
|
||||||
...(tagsToCheck.length && {
|
|
||||||
secretTags: tagsToCheck.map((el) => el.slug)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
);
|
});
|
||||||
|
|
||||||
return reshapeBridgeSecret(
|
return reshapeBridgeSecret(
|
||||||
projectId,
|
projectId,
|
||||||
@@ -651,15 +649,12 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
projectId
|
projectId
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretValueHidden = !permission.can(
|
const secretValueHidden = !CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath,
|
||||||
environment,
|
secretName: secretToDelete.key,
|
||||||
secretPath,
|
secretTags: secretToDelete.tags?.map((el) => el.slug)
|
||||||
secretName: secretToDelete.key,
|
});
|
||||||
secretTags: secretToDelete.tags?.map((el) => el.slug)
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return reshapeBridgeSecret(
|
return reshapeBridgeSecret(
|
||||||
projectId,
|
projectId,
|
||||||
@@ -702,11 +697,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
|
CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.DescribeSecret);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
|
||||||
ProjectPermissionSub.Secrets
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environments, path);
|
const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environments, path);
|
||||||
@@ -752,11 +743,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
|
CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.DescribeSecret);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
|
||||||
ProjectPermissionSub.Secrets
|
|
||||||
);
|
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||||
if (!folder) return 0;
|
if (!folder) return 0;
|
||||||
@@ -791,8 +778,20 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const decryptedSecrets = secrets
|
const decryptedSecrets = secrets
|
||||||
.filter((el) =>
|
.filter((el) => {
|
||||||
projectPermission.can(
|
if (
|
||||||
|
filterByAction === ProjectPermissionSecretActions.ReadValue ||
|
||||||
|
filterByAction === ProjectPermissionSecretActions.DescribeSecret
|
||||||
|
) {
|
||||||
|
return CheckCanSecretsSubject(projectPermission, filterByAction, {
|
||||||
|
environment: groupedFolderMappings[el.folderId][0].environment,
|
||||||
|
secretPath: groupedFolderMappings[el.folderId][0].path,
|
||||||
|
secretName: el.key,
|
||||||
|
secretTags: el.tags.map((i) => i.slug)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return projectPermission.can(
|
||||||
filterByAction,
|
filterByAction,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: groupedFolderMappings[el.folderId][0].environment,
|
environment: groupedFolderMappings[el.folderId][0].environment,
|
||||||
@@ -800,19 +799,16 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
secretName: el.key,
|
secretName: el.key,
|
||||||
secretTags: el.tags.map((i) => i.slug)
|
secretTags: el.tags.map((i) => i.slug)
|
||||||
})
|
})
|
||||||
)
|
);
|
||||||
)
|
})
|
||||||
.map((secret) => {
|
.map((secret) => {
|
||||||
// Note(Daniel): This is only relevant if the filterAction isn't set to ReadValue. This is needed for the frontend.
|
// Note(Daniel): This is only relevant if the filterAction isn't set to ReadValue. This is needed for the frontend.
|
||||||
const secretValueHidden = !projectPermission.can(
|
const secretValueHidden = !CheckCanSecretsSubject(projectPermission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment: groupedFolderMappings[secret.folderId][0].environment,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath: groupedFolderMappings[secret.folderId][0].path,
|
||||||
environment: groupedFolderMappings[secret.folderId][0].environment,
|
secretName: secret.key,
|
||||||
secretPath: groupedFolderMappings[secret.folderId][0].path,
|
secretTags: secret.tags.map((i) => i.slug)
|
||||||
secretName: secret.key,
|
});
|
||||||
secretTags: secret.tags.map((i) => i.slug)
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return reshapeBridgeSecret(
|
return reshapeBridgeSecret(
|
||||||
projectId,
|
projectId,
|
||||||
@@ -858,10 +854,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
if (!isInternal) {
|
if (!isInternal) {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.DescribeSecret);
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
|
||||||
ProjectPermissionSub.Secrets
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environments, path);
|
const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environments, path);
|
||||||
@@ -913,15 +906,11 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
|
CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.DescribeSecret, {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
environment,
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
secretPath: path,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretTags: params.tagSlugs
|
||||||
environment,
|
});
|
||||||
secretPath: path,
|
|
||||||
secretTags: params.tagSlugs
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
let paths: { folderId: string; path: string }[] = [];
|
let paths: { folderId: string; path: string }[] = [];
|
||||||
|
|
||||||
@@ -960,15 +949,12 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
|
|
||||||
const decryptedSecrets = secrets
|
const decryptedSecrets = secrets
|
||||||
.filter((el) => {
|
.filter((el) => {
|
||||||
const canDescribeSecret = permission.can(
|
const canDescribeSecret = CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.DescribeSecret, {
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
environment,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath: groupedPaths[el.folderId][0].path,
|
||||||
environment,
|
secretName: el.key,
|
||||||
secretPath: groupedPaths[el.folderId][0].path,
|
secretTags: el.tags.map((i) => i.slug)
|
||||||
secretName: el.key,
|
});
|
||||||
secretTags: el.tags.map((i) => i.slug)
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!canDescribeSecret) {
|
if (!canDescribeSecret) {
|
||||||
return false;
|
return false;
|
||||||
@@ -977,14 +963,15 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
if (viewSecretValue) {
|
if (viewSecretValue) {
|
||||||
// Recursive secret, should be filtered out
|
// Recursive secret, should be filtered out
|
||||||
if (groupedPaths[el.folderId][0].path !== path) {
|
if (groupedPaths[el.folderId][0].path !== path) {
|
||||||
const canReadRecursiveSecretValue = permission.can(
|
const canReadRecursiveSecretValue = CheckCanSecretsSubject(
|
||||||
|
permission,
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
{
|
||||||
environment,
|
environment,
|
||||||
secretPath: groupedPaths[el.folderId][0].path,
|
secretPath: groupedPaths[el.folderId][0].path,
|
||||||
secretName: el.key,
|
secretName: el.key,
|
||||||
secretTags: el.tags.map((i) => i.slug)
|
secretTags: el.tags.map((i) => i.slug)
|
||||||
})
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!canReadRecursiveSecretValue) {
|
if (!canReadRecursiveSecretValue) {
|
||||||
@@ -993,15 +980,12 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (throwOnMissingReadValuePermission) {
|
if (throwOnMissingReadValuePermission) {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath: groupedPaths[el.folderId][0].path,
|
||||||
environment,
|
secretName: el.key,
|
||||||
secretPath: groupedPaths[el.folderId][0].path,
|
secretTags: el.tags.map((i) => i.slug)
|
||||||
secretName: el.key,
|
});
|
||||||
secretTags: el.tags.map((i) => i.slug)
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
// Else, we do nothing. Because we don't want to filter out the secret, OR throw an error.
|
// Else, we do nothing. Because we don't want to filter out the secret, OR throw an error.
|
||||||
// If the user doesn't have access to read the value, in the below map function, we mask the secret value and return the secret with a hidden value.
|
// If the user doesn't have access to read the value, in the below map function, we mask the secret value and return the secret with a hidden value.
|
||||||
@@ -1014,15 +998,12 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
|
|
||||||
const secretValueHidden =
|
const secretValueHidden =
|
||||||
!viewSecretValue ||
|
!viewSecretValue ||
|
||||||
!permission.can(
|
!CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath: groupedPaths[secret.folderId][0].path,
|
||||||
environment,
|
secretName: secret.key,
|
||||||
secretPath: groupedPaths[secret.folderId][0].path,
|
secretTags: secret.tags.map((i) => i.slug)
|
||||||
secretName: secret.key,
|
});
|
||||||
secretTags: secret.tags.map((i) => i.slug)
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return reshapeBridgeSecret(
|
return reshapeBridgeSecret(
|
||||||
projectId,
|
projectId,
|
||||||
@@ -1047,15 +1028,12 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
secretDAL,
|
secretDAL,
|
||||||
decryptSecretValue: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined),
|
decryptSecretValue: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined),
|
||||||
canExpandValue: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) =>
|
canExpandValue: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) =>
|
||||||
permission.can(
|
CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment: expandEnvironment,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath: expandSecretPath,
|
||||||
environment: expandEnvironment,
|
secretName: expandSecretKey,
|
||||||
secretPath: expandSecretPath,
|
secretTags: expandSecretTags
|
||||||
secretName: expandSecretKey,
|
})
|
||||||
secretTags: expandSecretTags
|
|
||||||
})
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (shouldExpandSecretReferences) {
|
if (shouldExpandSecretReferences) {
|
||||||
@@ -1095,25 +1073,19 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
expandSecretReferences,
|
expandSecretReferences,
|
||||||
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""),
|
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""),
|
||||||
hasSecretAccess: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) => {
|
hasSecretAccess: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) => {
|
||||||
const canDescribe = permission.can(
|
const canDescribe = CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.DescribeSecret, {
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
environment: expandEnvironment,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath: expandSecretPath,
|
||||||
environment: expandEnvironment,
|
secretName: expandSecretKey,
|
||||||
secretPath: expandSecretPath,
|
secretTags: expandSecretTags
|
||||||
secretName: expandSecretKey,
|
});
|
||||||
secretTags: expandSecretTags
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const canReadValue = permission.can(
|
const canReadValue = CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment: expandEnvironment,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath: expandSecretPath,
|
||||||
environment: expandEnvironment,
|
secretName: expandSecretKey,
|
||||||
secretPath: expandSecretPath,
|
secretTags: expandSecretTags
|
||||||
secretName: expandSecretKey,
|
});
|
||||||
secretTags: expandSecretTags
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return viewSecretValue ? canDescribe && canReadValue : canDescribe;
|
return viewSecretValue ? canDescribe && canReadValue : canDescribe;
|
||||||
}
|
}
|
||||||
@@ -1264,15 +1236,12 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
})
|
})
|
||||||
));
|
));
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.DescribeSecret, {
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
environment,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath: path,
|
||||||
environment,
|
secretName,
|
||||||
secretPath: path,
|
secretTags: (secret?.tags || []).map((el) => el.slug)
|
||||||
secretName,
|
});
|
||||||
secretTags: (secret?.tags || []).map((el) => el.slug)
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// this will throw if the user doesn't have read value permission no matter what
|
// this will throw if the user doesn't have read value permission no matter what
|
||||||
// because if its an expansion, it will fully depend on the value.
|
// because if its an expansion, it will fully depend on the value.
|
||||||
@@ -1282,15 +1251,12 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
secretDAL,
|
secretDAL,
|
||||||
decryptSecretValue: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined),
|
decryptSecretValue: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined),
|
||||||
canExpandValue: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) => {
|
canExpandValue: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) => {
|
||||||
return permission.can(
|
return CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment: expandEnvironment,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath: expandSecretPath,
|
||||||
environment: expandEnvironment,
|
secretName: expandSecretKey,
|
||||||
secretPath: expandSecretPath,
|
secretTags: expandSecretTags
|
||||||
secretName: expandSecretKey,
|
});
|
||||||
secretTags: expandSecretTags
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1310,15 +1276,12 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""),
|
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""),
|
||||||
expandSecretReferences: shouldExpandSecretReferences ? expandSecretReferences : undefined,
|
expandSecretReferences: shouldExpandSecretReferences ? expandSecretReferences : undefined,
|
||||||
hasSecretAccess: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) => {
|
hasSecretAccess: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) => {
|
||||||
return permission.can(
|
return CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.DescribeSecret, {
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
environment: expandEnvironment,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath: expandSecretPath,
|
||||||
environment: expandEnvironment,
|
secretName: expandSecretKey,
|
||||||
secretPath: expandSecretPath,
|
secretTags: expandSecretTags
|
||||||
secretName: expandSecretKey,
|
});
|
||||||
secretTags: expandSecretTags
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1330,15 +1293,12 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
|
|
||||||
if (viewSecretValue) {
|
if (viewSecretValue) {
|
||||||
if (
|
if (
|
||||||
!permission.can(
|
!CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment: importedSecret.environment,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath: importedSecrets[i].secretPath,
|
||||||
environment: importedSecret.environment,
|
secretName: importedSecret.key,
|
||||||
secretPath: importedSecrets[i].secretPath,
|
secretTags: (importedSecret.secretTags || []).map((el) => el.slug)
|
||||||
secretName: importedSecret.key,
|
}) &&
|
||||||
secretTags: (importedSecret.secretTags || []).map((el) => el.slug)
|
|
||||||
})
|
|
||||||
) &&
|
|
||||||
secretType !== SecretType.Personal
|
secretType !== SecretType.Personal
|
||||||
) {
|
) {
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
@@ -1386,15 +1346,12 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
|
|
||||||
if (viewSecretValue) {
|
if (viewSecretValue) {
|
||||||
if (
|
if (
|
||||||
!permission.can(
|
!CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath: path,
|
||||||
environment,
|
secretName,
|
||||||
secretPath: path,
|
secretTags: (secret?.tags || []).map((el) => el.slug)
|
||||||
secretName,
|
}) &&
|
||||||
secretTags: (secret?.tags || []).map((el) => el.slug)
|
|
||||||
})
|
|
||||||
) &&
|
|
||||||
secretType !== SecretType.Personal
|
secretType !== SecretType.Personal
|
||||||
) {
|
) {
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
@@ -1562,15 +1519,12 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return newSecrets.map((el) => {
|
return newSecrets.map((el) => {
|
||||||
const secretValueHidden = !permission.can(
|
const secretValueHidden = !CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath,
|
||||||
environment,
|
secretName: el.key,
|
||||||
secretPath,
|
secretTags: el.tags?.map((i) => i.slug)
|
||||||
secretName: el.key,
|
});
|
||||||
secretTags: el.tags?.map((i) => i.slug)
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return reshapeBridgeSecret(
|
return reshapeBridgeSecret(
|
||||||
projectId,
|
projectId,
|
||||||
@@ -1899,15 +1853,12 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return updatedSecrets.map((el) => {
|
return updatedSecrets.map((el) => {
|
||||||
const secretValueHidden = !permission.can(
|
const secretValueHidden = !CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath: el.secretPath,
|
||||||
environment,
|
secretName: el.key,
|
||||||
secretPath: el.secretPath,
|
secretTags: el.tags.map((i) => i.slug)
|
||||||
secretName: el.key,
|
});
|
||||||
secretTags: el.tags.map((i) => i.slug)
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...reshapeBridgeSecret(
|
...reshapeBridgeSecret(
|
||||||
@@ -2032,15 +1983,12 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
|
|
||||||
const secretValueHidden =
|
const secretValueHidden =
|
||||||
!secretToDeleteMatch ||
|
!secretToDeleteMatch ||
|
||||||
!permission.can(
|
!CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath,
|
||||||
environment,
|
secretName: el.key,
|
||||||
secretPath,
|
secretTags: secretToDeleteMatch.tags?.map((i) => i.slug)
|
||||||
secretName: el.key,
|
});
|
||||||
secretTags: secretToDeleteMatch.tags?.map((i) => i.slug)
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return reshapeBridgeSecret(
|
return reshapeBridgeSecret(
|
||||||
projectId,
|
projectId,
|
||||||
@@ -2097,17 +2045,15 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
sort: [["createdAt", "desc"]]
|
sort: [["createdAt", "desc"]]
|
||||||
});
|
});
|
||||||
return secretVersions.map((el) => {
|
return secretVersions.map((el) => {
|
||||||
const secretValueHidden = permission.cannot(
|
const secretValueHidden = !CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment: folder.environment.envSlug,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath: folderWithPath.path,
|
||||||
environment: folder.environment.envSlug,
|
secretName: el.key,
|
||||||
secretPath: folderWithPath.path,
|
...(el.tags?.length && {
|
||||||
secretName: el.key,
|
secretTags: el.tags.map((tag) => tag.slug)
|
||||||
...(el.tags?.length && {
|
|
||||||
secretTags: el.tags.map((tag) => tag.slug)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
);
|
});
|
||||||
|
|
||||||
return reshapeBridgeSecret(
|
return reshapeBridgeSecret(
|
||||||
folder.projectId,
|
folder.projectId,
|
||||||
folder.environment.envSlug,
|
folder.environment.envSlug,
|
||||||
@@ -2224,15 +2170,27 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
|
|
||||||
sourceSecrets.forEach((secret) => {
|
sourceSecrets.forEach((secret) => {
|
||||||
for (const sourceAction of sourceActions) {
|
for (const sourceAction of sourceActions) {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
if (
|
||||||
sourceAction,
|
sourceAction === ProjectPermissionSecretActions.DescribeSecret ||
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
sourceAction === ProjectPermissionSecretActions.ReadValue
|
||||||
|
) {
|
||||||
|
CheckForbiddenErrorSecretsSubject(permission, sourceAction, {
|
||||||
environment: sourceEnvironment,
|
environment: sourceEnvironment,
|
||||||
secretPath: sourceSecretPath,
|
secretPath: sourceSecretPath,
|
||||||
secretName: secret.key,
|
secretName: secret.key,
|
||||||
secretTags: secret.tags.map((el) => el.slug)
|
secretTags: secret.tags.map((el) => el.slug)
|
||||||
})
|
});
|
||||||
);
|
} else {
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
sourceAction,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment: sourceEnvironment,
|
||||||
|
secretPath: sourceSecretPath,
|
||||||
|
secretName: secret.key,
|
||||||
|
secretTags: secret.tags.map((el) => el.slug)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2555,10 +2513,10 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.DescribeSecret, {
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
environment,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
secretPath
|
||||||
);
|
});
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
||||||
if (!folder)
|
if (!folder)
|
||||||
@@ -2579,15 +2537,12 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
type: SecretType.Shared
|
type: SecretType.Shared
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.DescribeSecret, {
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
environment,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath,
|
||||||
environment,
|
secretName,
|
||||||
secretPath,
|
secretTags: (secret?.tags || []).map((el) => el.slug)
|
||||||
secretName,
|
});
|
||||||
secretTags: (secret?.tags || []).map((el) => el.slug)
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const decryptedSecretValue = secret.encryptedValue
|
const decryptedSecretValue = secret.encryptedValue
|
||||||
? secretManagerDecryptor({ cipherTextBlob: secret.encryptedValue }).toString()
|
? secretManagerDecryptor({ cipherTextBlob: secret.encryptedValue }).toString()
|
||||||
@@ -2599,27 +2554,21 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
secretDAL,
|
secretDAL,
|
||||||
decryptSecretValue: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined),
|
decryptSecretValue: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined),
|
||||||
canExpandValue: (expandEnvironment, expandSecretPath, expandSecretName, expandSecretTags) =>
|
canExpandValue: (expandEnvironment, expandSecretPath, expandSecretName, expandSecretTags) =>
|
||||||
permission.can(
|
CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment: expandEnvironment,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath: expandSecretPath,
|
||||||
environment: expandEnvironment,
|
secretName: expandSecretName,
|
||||||
secretPath: expandSecretPath,
|
secretTags: expandSecretTags
|
||||||
secretName: expandSecretName,
|
})
|
||||||
secretTags: expandSecretTags
|
|
||||||
})
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!permission.can(
|
!CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath,
|
||||||
environment,
|
secretName,
|
||||||
secretPath,
|
secretTags: (secret?.tags || []).map((el) => el.slug)
|
||||||
secretName,
|
})
|
||||||
secretTags: (secret?.tags || []).map((el) => el.slug)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
message: `Unable to get secret reference tree for secret with key '${secretName}', because you don't have permission to view secret value.`
|
message: `Unable to get secret reference tree for secret with key '${secretName}', because you don't have permission to view secret value.`
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
/* eslint-disable no-await-in-loop */
|
/* eslint-disable no-await-in-loop */
|
||||||
import { subject } from "@casl/ability";
|
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -12,8 +11,9 @@ import {
|
|||||||
TSecretFolders,
|
TSecretFolders,
|
||||||
TSecrets
|
TSecrets
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
|
import { CheckCanSecretsSubject } from "@app/ee/services/permission/permission-fns";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionSecretActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionSecretActions } from "@app/ee/services/permission/project-permission";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import {
|
import {
|
||||||
buildSecretBlindIndexFromName,
|
buildSecretBlindIndexFromName,
|
||||||
@@ -191,13 +191,10 @@ export const recursivelyGetSecretPaths = ({
|
|||||||
// Filter out paths that the user does not have permission to access, and paths that are not in the current path
|
// Filter out paths that the user does not have permission to access, and paths that are not in the current path
|
||||||
const allowedPaths = paths.filter(
|
const allowedPaths = paths.filter(
|
||||||
(folder) =>
|
(folder) =>
|
||||||
permission.can(
|
CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath: folder.path
|
||||||
environment,
|
}) && folder.path.startsWith(currentPath === "/" ? "" : currentPath)
|
||||||
secretPath: folder.path
|
|
||||||
})
|
|
||||||
) && folder.path.startsWith(currentPath === "/" ? "" : currentPath)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return allowedPaths;
|
return allowedPaths;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
SecretType
|
SecretType
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
|
import { CheckCanSecretsSubject, CheckForbiddenErrorSecretsSubject } from "@app/ee/services/permission/permission-fns";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
@@ -452,13 +453,10 @@ export const secretServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const secretValueHidden = !permission.can(
|
const secretValueHidden = !CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath: path
|
||||||
environment,
|
});
|
||||||
secretPath: path
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...updatedSecret[0],
|
...updatedSecret[0],
|
||||||
@@ -562,10 +560,10 @@ export const secretServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const secretValueHidden = !permission.can(
|
const secretValueHidden = !CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
secretPath: path
|
||||||
);
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...deletedSecret[0],
|
...deletedSecret[0],
|
||||||
@@ -622,10 +620,10 @@ export const secretServiceFactory = ({
|
|||||||
|
|
||||||
paths = deepPaths.map(({ folderId, path: p }) => ({ folderId, path: p }));
|
paths = deepPaths.map(({ folderId, path: p }) => ({ folderId, path: p }));
|
||||||
} else {
|
} else {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
secretPath: path
|
||||||
);
|
});
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||||
if (!folder) return { secrets: [], imports: [] };
|
if (!folder) return { secrets: [], imports: [] };
|
||||||
@@ -647,13 +645,10 @@ export const secretServiceFactory = ({
|
|||||||
// if its service token allow full access over imported one
|
// if its service token allow full access over imported one
|
||||||
actor === ActorType.SERVICE
|
actor === ActorType.SERVICE
|
||||||
? true
|
? true
|
||||||
: permission.can(
|
: CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment: importEnv.slug,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath: importPath
|
||||||
environment: importEnv.slug,
|
})
|
||||||
secretPath: importPath
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
const importedSecrets = await fnSecretsFromImports({
|
const importedSecrets = await fnSecretsFromImports({
|
||||||
allowedImports,
|
allowedImports,
|
||||||
@@ -704,10 +699,11 @@ export const secretServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
secretPath: path
|
||||||
);
|
});
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||||
if (!folder)
|
if (!folder)
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
@@ -754,14 +750,12 @@ export const secretServiceFactory = ({
|
|||||||
// if its service token allow full access over imported one
|
// if its service token allow full access over imported one
|
||||||
actor === ActorType.SERVICE
|
actor === ActorType.SERVICE
|
||||||
? true
|
? true
|
||||||
: permission.can(
|
: CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment: importEnv.slug,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath: importPath
|
||||||
environment: importEnv.slug,
|
})
|
||||||
secretPath: importPath
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const importedSecrets = await fnSecretsFromImports({
|
const importedSecrets = await fnSecretsFromImports({
|
||||||
allowedImports,
|
allowedImports,
|
||||||
secretDAL,
|
secretDAL,
|
||||||
@@ -975,10 +969,10 @@ export const secretServiceFactory = ({
|
|||||||
secretVersionTagDAL
|
secretVersionTagDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretValueHidden = !permission.can(
|
const secretValueHidden = !CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
secretPath: path
|
||||||
);
|
});
|
||||||
|
|
||||||
return updatedSecrets.map((secret) => ({
|
return updatedSecrets.map((secret) => ({
|
||||||
...secret,
|
...secret,
|
||||||
@@ -1069,11 +1063,10 @@ export const secretServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const secretValueHidden = !CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
const secretValueHidden = !permission.can(
|
environment,
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
secretPath: path
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
});
|
||||||
);
|
|
||||||
|
|
||||||
return secrets.map((secret) => ({
|
return secrets.map((secret) => ({
|
||||||
...secret,
|
...secret,
|
||||||
@@ -1259,8 +1252,20 @@ export const secretServiceFactory = ({
|
|||||||
ProjectPermissionSecretActions.Delete,
|
ProjectPermissionSecretActions.Delete,
|
||||||
ProjectPermissionSecretActions.Create,
|
ProjectPermissionSecretActions.Create,
|
||||||
ProjectPermissionSecretActions.Edit
|
ProjectPermissionSecretActions.Edit
|
||||||
].filter((action) =>
|
].filter((action) => {
|
||||||
entityPermission.permission.can(
|
if (
|
||||||
|
action === ProjectPermissionSecretActions.DescribeSecret ||
|
||||||
|
action === ProjectPermissionSecretActions.ReadValue
|
||||||
|
) {
|
||||||
|
return CheckCanSecretsSubject(entityPermission.permission, action, {
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
secretName,
|
||||||
|
secretTags: secret?.tags?.map((el) => el.slug)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return entityPermission.permission.can(
|
||||||
action,
|
action,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment,
|
||||||
@@ -1268,8 +1273,8 @@ export const secretServiceFactory = ({
|
|||||||
secretName,
|
secretName,
|
||||||
secretTags: secret?.tags?.map((el) => el.slug)
|
secretTags: secret?.tags?.map((el) => el.slug)
|
||||||
})
|
})
|
||||||
)
|
);
|
||||||
);
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...entityPermission,
|
...entityPermission,
|
||||||
@@ -2424,17 +2429,14 @@ export const secretServiceFactory = ({
|
|||||||
key: botKey
|
key: botKey
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretValueHidden = permission.cannot(
|
const secretValueHidden = !CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
environment: folder.environment.envSlug,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath: folderWithPath.path,
|
||||||
environment: folder.environment.envSlug,
|
secretName: secretKey,
|
||||||
secretPath: folderWithPath.path,
|
...(el.tags?.length && {
|
||||||
secretName: secretKey,
|
secretTags: el.tags.map((tag) => tag.slug)
|
||||||
...(el.tags?.length && {
|
|
||||||
secretTags: el.tags.map((tag) => tag.slug)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
);
|
});
|
||||||
|
|
||||||
return decryptSecretRaw(
|
return decryptSecretRaw(
|
||||||
{
|
{
|
||||||
@@ -2833,13 +2835,23 @@ export const secretServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const sourceAction of sourceActions) {
|
for (const sourceAction of sourceActions) {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
if (
|
||||||
sourceAction,
|
sourceAction === ProjectPermissionSecretActions.ReadValue ||
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
sourceAction === ProjectPermissionSecretActions.DescribeSecret
|
||||||
|
) {
|
||||||
|
CheckForbiddenErrorSecretsSubject(permission, sourceAction, {
|
||||||
environment: sourceEnvironment,
|
environment: sourceEnvironment,
|
||||||
secretPath: sourceSecretPath
|
secretPath: sourceSecretPath
|
||||||
})
|
});
|
||||||
);
|
} else {
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
sourceAction,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment: sourceEnvironment,
|
||||||
|
secretPath: sourceSecretPath
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export type TCreateServiceTokenDTO = {
|
|||||||
iv: string;
|
iv: string;
|
||||||
tag: string;
|
tag: string;
|
||||||
expiresIn?: number | null;
|
expiresIn?: number | null;
|
||||||
permissions: ("read" | "write" | "readValue")[];
|
permissions: ("read" | "write")[];
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|
||||||
export type TGetServiceTokenInfoDTO = Omit<TProjectPermission, "projectId">;
|
export type TGetServiceTokenInfoDTO = Omit<TProjectPermission, "projectId">;
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ export enum ProjectPermissionActions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionSecretActions {
|
export enum ProjectPermissionSecretActions {
|
||||||
DescribeSecret = "read",
|
DescribeAndReadValue = "read",
|
||||||
|
DescribeSecret = "describeSecret",
|
||||||
ReadValue = "readValue",
|
ReadValue = "readValue",
|
||||||
Create = "create",
|
Create = "create",
|
||||||
Edit = "edit",
|
Edit = "edit",
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { MongoAbility, subject } from "@casl/ability";
|
||||||
|
|
||||||
|
import { ProjectPermissionSet } from "@app/context/ProjectPermissionContext";
|
||||||
|
import {
|
||||||
|
ProjectPermissionSecretActions,
|
||||||
|
ProjectPermissionSub,
|
||||||
|
SecretSubjectFields
|
||||||
|
} from "@app/context/ProjectPermissionContext/types";
|
||||||
|
|
||||||
|
export function secretsPermissionCan(
|
||||||
|
permission: MongoAbility<ProjectPermissionSet>,
|
||||||
|
action: Extract<
|
||||||
|
ProjectPermissionSecretActions,
|
||||||
|
ProjectPermissionSecretActions.DescribeSecret | ProjectPermissionSecretActions.ReadValue
|
||||||
|
>,
|
||||||
|
subjectFields?: SecretSubjectFields
|
||||||
|
) {
|
||||||
|
let canNewPermission = false;
|
||||||
|
let canOldPermission = false;
|
||||||
|
|
||||||
|
if (subjectFields) {
|
||||||
|
canNewPermission = permission.can(action, subject(ProjectPermissionSub.Secrets, subjectFields));
|
||||||
|
canOldPermission = permission.can(
|
||||||
|
ProjectPermissionSecretActions.DescribeAndReadValue,
|
||||||
|
subject(ProjectPermissionSub.Secrets, subjectFields)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
canNewPermission = permission.can(action, ProjectPermissionSub.Secrets);
|
||||||
|
canOldPermission = permission.can(
|
||||||
|
ProjectPermissionSecretActions.DescribeAndReadValue,
|
||||||
|
ProjectPermissionSub.Secrets
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return canNewPermission || canOldPermission;
|
||||||
|
}
|
||||||
+1
-5
@@ -303,13 +303,9 @@ export const AddServiceTokenModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
render={({ field: { onChange, value }, fieldState: { error } }) => {
|
render={({ field: { onChange, value }, fieldState: { error } }) => {
|
||||||
const options = [
|
const options = [
|
||||||
{
|
{
|
||||||
label: "Describe Secret (default)",
|
label: "Read (default)",
|
||||||
value: "read"
|
value: "read"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: "Read Value (optional)",
|
|
||||||
value: "readValue"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: "Write (optional)",
|
label: "Write (optional)",
|
||||||
value: "write"
|
value: "write"
|
||||||
|
|||||||
+128
-108
@@ -35,12 +35,13 @@ export const GeneralPermissionPolicies = <T extends keyof NonNullable<TFormSchem
|
|||||||
title,
|
title,
|
||||||
isDisabled
|
isDisabled
|
||||||
}: Props<T>) => {
|
}: Props<T>) => {
|
||||||
const { control } = useFormContext<TFormSchema>();
|
const { control, watch } = useFormContext<TFormSchema>();
|
||||||
const items = useFieldArray({
|
const items = useFieldArray({
|
||||||
control,
|
control,
|
||||||
name: `permissions.${subject}`
|
name: `permissions.${subject}`
|
||||||
});
|
});
|
||||||
const [isOpen, setIsOpen] = useToggle();
|
const [isOpen, setIsOpen] = useToggle();
|
||||||
|
// const [hideFullReadAccess, setHideFullReadAccess] = useState(false);
|
||||||
|
|
||||||
if (!items.fields.length) return <div />;
|
if (!items.fields.length) return <div />;
|
||||||
|
|
||||||
@@ -71,119 +72,138 @@ export const GeneralPermissionPolicies = <T extends keyof NonNullable<TFormSchem
|
|||||||
</div>
|
</div>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div key={`select-${subject}-type`} className="flex flex-col space-y-4 bg-bunker-800 p-6">
|
<div key={`select-${subject}-type`} className="flex flex-col space-y-4 bg-bunker-800 p-6">
|
||||||
{items.fields.map((el, rootIndex) => (
|
{items.fields.map((el, rootIndex) => {
|
||||||
<div key={el.id} className="bg-mineshaft-800 p-5 first:rounded-t-md last:rounded-b-md">
|
let isFullReadAccessEnabled = false;
|
||||||
{isConditionalSubjects(subject) && (
|
|
||||||
<div className="mb-6 mt-4 flex w-full items-center text-gray-300">
|
|
||||||
<div className="w-1/4">Permission</div>
|
|
||||||
<div className="mr-4 w-1/4">
|
|
||||||
<Controller
|
|
||||||
defaultValue={false as any}
|
|
||||||
name={`permissions.${subject}.${rootIndex}.inverted`}
|
|
||||||
render={({ field }) => (
|
|
||||||
<Select
|
|
||||||
value={String(field.value)}
|
|
||||||
onValueChange={(val) => field.onChange(val === "true")}
|
|
||||||
containerClassName="w-full"
|
|
||||||
className="w-full"
|
|
||||||
isDisabled={isDisabled}
|
|
||||||
>
|
|
||||||
<SelectItem value="false">Allow</SelectItem>
|
|
||||||
<SelectItem value="true">Forbid</SelectItem>
|
|
||||||
</Select>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Tooltip
|
|
||||||
asChild
|
|
||||||
content={
|
|
||||||
<>
|
|
||||||
<p>
|
|
||||||
Whether to allow or forbid the selected actions when the following
|
|
||||||
conditions (if any) are met.
|
|
||||||
</p>
|
|
||||||
<p className="mt-2">Forbid rules must come after allow rules.</p>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faInfoCircle} size="sm" className="text-gray-400" />
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="flex text-gray-300">
|
|
||||||
<div className="w-1/4">Actions</div>
|
|
||||||
<div className="flex flex-grow flex-wrap justify-start gap-8">
|
|
||||||
{actions.map(({ label, value }) => {
|
|
||||||
if (typeof value !== "string") return undefined;
|
|
||||||
|
|
||||||
return (
|
if (subject === ProjectPermissionSub.Secrets) {
|
||||||
|
isFullReadAccessEnabled = watch(`permissions.${subject}.${rootIndex}.read` as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={el.id}
|
||||||
|
className="bg-mineshaft-800 p-5 first:rounded-t-md last:rounded-b-md"
|
||||||
|
>
|
||||||
|
{isConditionalSubjects(subject) && (
|
||||||
|
<div className="mb-6 mt-4 flex w-full items-center text-gray-300">
|
||||||
|
<div className="w-1/4">Permission</div>
|
||||||
|
<div className="mr-4 w-1/4">
|
||||||
<Controller
|
<Controller
|
||||||
key={`${el.id}-${label}`}
|
defaultValue={false as any}
|
||||||
name={`permissions.${subject}.${rootIndex}.${value}` as any}
|
name={`permissions.${subject}.${rootIndex}.inverted`}
|
||||||
control={control}
|
render={({ field }) => (
|
||||||
defaultValue={false}
|
<Select
|
||||||
render={({ field }) => {
|
value={String(field.value)}
|
||||||
return (
|
onValueChange={(val) => field.onChange(val === "true")}
|
||||||
<div className="flex items-center justify-center">
|
containerClassName="w-full"
|
||||||
<Checkbox
|
className="w-full"
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
isChecked={Boolean(field.value)}
|
>
|
||||||
onCheckedChange={field.onChange}
|
<SelectItem value="false">Allow</SelectItem>
|
||||||
id={`permissions.${subject}.${rootIndex}.${String(value)}`}
|
<SelectItem value="true">Forbid</SelectItem>
|
||||||
>
|
</Select>
|
||||||
{label}
|
)}
|
||||||
</Checkbox>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
</div>
|
||||||
|
<div>
|
||||||
|
<Tooltip
|
||||||
|
asChild
|
||||||
|
content={
|
||||||
|
<>
|
||||||
|
<p>
|
||||||
|
Whether to allow or forbid the selected actions when the following
|
||||||
|
conditions (if any) are met.
|
||||||
|
</p>
|
||||||
|
<p className="mt-2">Forbid rules must come after allow rules.</p>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faInfoCircle} size="sm" className="text-gray-400" />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="flex text-gray-300">
|
||||||
|
<div className="w-1/4">Actions</div>
|
||||||
|
<div className="flex flex-grow flex-wrap justify-start gap-8">
|
||||||
|
{actions.map(({ label, value }, index) => {
|
||||||
|
if (typeof value !== "string") return undefined;
|
||||||
|
|
||||||
|
if (
|
||||||
|
subject === ProjectPermissionSub.Secrets &&
|
||||||
|
value === "read" &&
|
||||||
|
!isFullReadAccessEnabled
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Controller
|
||||||
|
key={`${el.id}-${index + 1}`}
|
||||||
|
name={`permissions.${subject}.${rootIndex}.${value}` as any}
|
||||||
|
control={control}
|
||||||
|
defaultValue={false}
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<Checkbox
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
isChecked={Boolean(field.value)}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
id={`permissions.${subject}.${rootIndex}.${String(value)}`}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{children &&
|
||||||
|
cloneElement(children, {
|
||||||
|
position: rootIndex
|
||||||
})}
|
})}
|
||||||
|
<div
|
||||||
|
className={twMerge(
|
||||||
|
"mt-4 flex justify-start space-x-4",
|
||||||
|
isConditionalSubjects(subject) && "justify-end"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{!isDisabled && isConditionalSubjects(subject) && (
|
||||||
|
<Button
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
|
variant="star"
|
||||||
|
size="xs"
|
||||||
|
className="mt-2"
|
||||||
|
onClick={() => {
|
||||||
|
items.insert(rootIndex + 1, [
|
||||||
|
{ read: false, edit: false, create: false, delete: false } as any
|
||||||
|
]);
|
||||||
|
}}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
>
|
||||||
|
Add policy
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{!isDisabled && (
|
||||||
|
<Button
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faTrash} />}
|
||||||
|
variant="outline_bg"
|
||||||
|
size="xs"
|
||||||
|
className="mt-2 hover:border-red"
|
||||||
|
onClick={() => items.remove(rootIndex)}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
>
|
||||||
|
Remove policy
|
||||||
|
</Button>
|
||||||
|
)}{" "}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{children &&
|
);
|
||||||
cloneElement(children, {
|
})}
|
||||||
position: rootIndex
|
|
||||||
})}
|
|
||||||
<div
|
|
||||||
className={twMerge(
|
|
||||||
"mt-4 flex justify-start space-x-4",
|
|
||||||
isConditionalSubjects(subject) && "justify-end"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{!isDisabled && isConditionalSubjects(subject) && (
|
|
||||||
<Button
|
|
||||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
|
||||||
variant="star"
|
|
||||||
size="xs"
|
|
||||||
className="mt-2"
|
|
||||||
onClick={() => {
|
|
||||||
items.insert(rootIndex + 1, [
|
|
||||||
{ read: false, edit: false, create: false, delete: false } as any
|
|
||||||
]);
|
|
||||||
}}
|
|
||||||
isDisabled={isDisabled}
|
|
||||||
>
|
|
||||||
Add policy
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{!isDisabled && (
|
|
||||||
<Button
|
|
||||||
leftIcon={<FontAwesomeIcon icon={faTrash} />}
|
|
||||||
variant="outline_bg"
|
|
||||||
size="xs"
|
|
||||||
className="mt-2 hover:border-red"
|
|
||||||
onClick={() => items.remove(rootIndex)}
|
|
||||||
isDisabled={isDisabled}
|
|
||||||
>
|
|
||||||
Remove policy
|
|
||||||
</Button>
|
|
||||||
)}{" "}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+49
-15
@@ -1,5 +1,9 @@
|
|||||||
|
import { ReactNode } from "react";
|
||||||
|
import { faWarning } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { Tooltip } from "@app/components/v2";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
ProjectPermissionCmekActions,
|
ProjectPermissionCmekActions,
|
||||||
@@ -24,11 +28,12 @@ const GeneralPolicyActionSchema = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const SecretPolicyActionSchema = z.object({
|
const SecretPolicyActionSchema = z.object({
|
||||||
read: z.boolean().optional(), // describe secret
|
[ProjectPermissionSecretActions.DescribeAndReadValue]: z.boolean().optional(), // existing read, gives both describe and read value
|
||||||
edit: z.boolean().optional(),
|
[ProjectPermissionSecretActions.DescribeSecret]: z.boolean().optional(), // describe secret, cannot read value
|
||||||
delete: z.boolean().optional(),
|
[ProjectPermissionSecretActions.ReadValue]: z.boolean().optional(), // read value
|
||||||
create: z.boolean().optional(),
|
[ProjectPermissionSecretActions.Edit]: z.boolean().optional(), // edit secret
|
||||||
readValue: z.boolean().optional()
|
[ProjectPermissionSecretActions.Delete]: z.boolean().optional(), // delete secret
|
||||||
|
[ProjectPermissionSecretActions.Create]: z.boolean().optional() // create secret
|
||||||
});
|
});
|
||||||
|
|
||||||
const CmekPolicyActionSchema = z.object({
|
const CmekPolicyActionSchema = z.object({
|
||||||
@@ -294,19 +299,24 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (subject === ProjectPermissionSub.Secrets) {
|
if (subject === ProjectPermissionSub.Secrets) {
|
||||||
const canRead = action.includes(ProjectPermissionSecretActions.DescribeSecret);
|
const canDescribeAndReadValue = action.includes(
|
||||||
|
ProjectPermissionSecretActions.DescribeAndReadValue
|
||||||
|
);
|
||||||
|
const canDescribe = action.includes(ProjectPermissionSecretActions.DescribeSecret);
|
||||||
|
const canReadValue = action.includes(ProjectPermissionSecretActions.ReadValue);
|
||||||
|
|
||||||
const canEdit = action.includes(ProjectPermissionSecretActions.Edit);
|
const canEdit = action.includes(ProjectPermissionSecretActions.Edit);
|
||||||
const canDelete = action.includes(ProjectPermissionSecretActions.Delete);
|
const canDelete = action.includes(ProjectPermissionSecretActions.Delete);
|
||||||
const canCreate = action.includes(ProjectPermissionSecretActions.Create);
|
const canCreate = action.includes(ProjectPermissionSecretActions.Create);
|
||||||
const canReadValue = action.includes(ProjectPermissionSecretActions.ReadValue);
|
|
||||||
|
|
||||||
// from above statement we are sure it won't be undefined
|
// from above statement we are sure it won't be undefined
|
||||||
formVal[subject]!.push({
|
formVal[subject]!.push({
|
||||||
read: canRead,
|
describeSecret: canDescribe,
|
||||||
|
read: canDescribeAndReadValue,
|
||||||
|
readValue: canReadValue,
|
||||||
create: canCreate,
|
create: canCreate,
|
||||||
edit: canEdit,
|
edit: canEdit,
|
||||||
delete: canDelete,
|
delete: canDelete,
|
||||||
readValue: canReadValue,
|
|
||||||
conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [],
|
conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [],
|
||||||
inverted
|
inverted
|
||||||
});
|
});
|
||||||
@@ -501,7 +511,7 @@ export type TProjectPermissionObject = {
|
|||||||
[K in ProjectPermissionSub]: {
|
[K in ProjectPermissionSub]: {
|
||||||
title: string;
|
title: string;
|
||||||
actions: {
|
actions: {
|
||||||
label: string;
|
label: string | ReactNode;
|
||||||
value: keyof Omit<
|
value: keyof Omit<
|
||||||
NonNullable<NonNullable<TFormSchema["permissions"]>[K]>[number],
|
NonNullable<NonNullable<TFormSchema["permissions"]>[K]>[number],
|
||||||
"conditions" | "inverted"
|
"conditions" | "inverted"
|
||||||
@@ -514,11 +524,35 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
|
|||||||
[ProjectPermissionSub.Secrets]: {
|
[ProjectPermissionSub.Secrets]: {
|
||||||
title: "Secrets",
|
title: "Secrets",
|
||||||
actions: [
|
actions: [
|
||||||
{ label: "Describe Secret", value: "read" },
|
{
|
||||||
{ label: "Create", value: "create" },
|
label: (
|
||||||
{ label: "Read Value", value: "readValue" },
|
<div className="flex items-center gap-1.5">
|
||||||
{ label: "Modify", value: "edit" },
|
<p className="opacity-60">
|
||||||
{ label: "Remove", value: "delete" }
|
Read <span className="text-xs opacity-80">(legacy)</span>
|
||||||
|
</p>
|
||||||
|
<Tooltip
|
||||||
|
className="overflow-hidden whitespace-normal"
|
||||||
|
content={
|
||||||
|
<div>
|
||||||
|
This is a legacy action and will be removed in the future.
|
||||||
|
<br />
|
||||||
|
<br /> You should instead use the{" "}
|
||||||
|
<strong className="font-semibold">Describe Secret</strong> and{" "}
|
||||||
|
<strong className="font-semibold">Read Value</strong> actions.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faWarning} className="mt-1 text-yellow-500" size="sm" />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
value: ProjectPermissionSecretActions.DescribeAndReadValue
|
||||||
|
},
|
||||||
|
{ label: "Describe Secret", value: ProjectPermissionSecretActions.DescribeSecret },
|
||||||
|
{ label: "Read Value", value: ProjectPermissionSecretActions.ReadValue },
|
||||||
|
{ label: "Modify", value: ProjectPermissionSecretActions.Edit },
|
||||||
|
{ label: "Remove", value: ProjectPermissionSecretActions.Delete },
|
||||||
|
{ label: "Create", value: ProjectPermissionSecretActions.Create }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
[ProjectPermissionSub.SecretFolders]: {
|
[ProjectPermissionSub.SecretFolders]: {
|
||||||
|
|||||||
+6
-2
@@ -15,6 +15,7 @@ import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionCo
|
|||||||
import { useToggle } from "@app/hooks";
|
import { useToggle } from "@app/hooks";
|
||||||
import { useUpdateSecretV3 } from "@app/hooks/api";
|
import { useUpdateSecretV3 } from "@app/hooks/api";
|
||||||
import { SecretType, SecretV3RawSanitized } from "@app/hooks/api/types";
|
import { SecretType, SecretV3RawSanitized } from "@app/hooks/api/types";
|
||||||
|
import { secretsPermissionCan } from "@app/lib/fn/permission";
|
||||||
|
|
||||||
enum SecretActionType {
|
enum SecretActionType {
|
||||||
Created = "created",
|
Created = "created",
|
||||||
@@ -51,8 +52,11 @@ function SecretRenameRow({ environments, getSecretByKey, secretKey, secretPath }
|
|||||||
secretTags: (secretDetails?.tags || []).map((i) => i.slug)
|
secretTags: (secretDetails?.tags || []).map((i) => i.slug)
|
||||||
});
|
});
|
||||||
const isSecretInEnvReadOnly =
|
const isSecretInEnvReadOnly =
|
||||||
permission.can(ProjectPermissionSecretActions.DescribeSecret, secretPermissionSubject) &&
|
secretsPermissionCan(
|
||||||
permission.cannot(ProjectPermissionSecretActions.Edit, secretPermissionSubject);
|
permission,
|
||||||
|
ProjectPermissionSecretActions.DescribeSecret,
|
||||||
|
secretPermissionSubject
|
||||||
|
) && permission.cannot(ProjectPermissionSecretActions.Edit, secretPermissionSubject);
|
||||||
if (isSecretInEnvReadOnly) {
|
if (isSecretInEnvReadOnly) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import { useGetProjectSecretsDetails } from "@app/hooks/api/dashboard";
|
|||||||
import { DashboardSecretsOrderBy } from "@app/hooks/api/dashboard/types";
|
import { DashboardSecretsOrderBy } from "@app/hooks/api/dashboard/types";
|
||||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||||
|
import { secretsPermissionCan } from "@app/lib/fn/permission";
|
||||||
|
|
||||||
import { SecretTableResourceCount } from "../OverviewPage/components/SecretTableResourceCount";
|
import { SecretTableResourceCount } from "../OverviewPage/components/SecretTableResourceCount";
|
||||||
import { SecretV2MigrationSection } from "../OverviewPage/components/SecretV2MigrationSection";
|
import { SecretV2MigrationSection } from "../OverviewPage/components/SecretV2MigrationSection";
|
||||||
@@ -103,23 +104,27 @@ const Page = () => {
|
|||||||
const workspaceId = currentWorkspace?.id || "";
|
const workspaceId = currentWorkspace?.id || "";
|
||||||
const projectSlug = currentWorkspace?.slug || "";
|
const projectSlug = currentWorkspace?.slug || "";
|
||||||
const secretPath = (routerQueryParams.secretPath as string) || "/";
|
const secretPath = (routerQueryParams.secretPath as string) || "/";
|
||||||
const canReadSecret = permission.can(
|
|
||||||
|
const canReadSecret = secretsPermissionCan(
|
||||||
|
permission,
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
ProjectPermissionSecretActions.DescribeSecret,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
{
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
secretName: "*",
|
secretName: "*",
|
||||||
secretTags: ["*"]
|
secretTags: ["*"]
|
||||||
})
|
}
|
||||||
);
|
);
|
||||||
const canReadSecretValue = permission.can(
|
|
||||||
|
const canReadSecretValue = secretsPermissionCan(
|
||||||
|
permission,
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
{
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
secretName: "*",
|
secretName: "*",
|
||||||
secretTags: ["*"]
|
secretTags: ["*"]
|
||||||
})
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const canReadSecretImports = permission.can(
|
const canReadSecretImports = permission.can(
|
||||||
|
|||||||
+11
-12
@@ -57,6 +57,7 @@ import { ActorType } from "@app/hooks/api/auditLogs/enums";
|
|||||||
import { useGetSecretAccessList } from "@app/hooks/api/secrets/queries";
|
import { useGetSecretAccessList } from "@app/hooks/api/secrets/queries";
|
||||||
import { SecretV3RawSanitized, WsTag } from "@app/hooks/api/types";
|
import { SecretV3RawSanitized, WsTag } from "@app/hooks/api/types";
|
||||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||||
|
import { secretsPermissionCan } from "@app/lib/fn/permission";
|
||||||
|
|
||||||
import { CreateReminderForm } from "./CreateReminderForm";
|
import { CreateReminderForm } from "./CreateReminderForm";
|
||||||
import { formSchema, SecretActionType, TFormSchema } from "./SecretListView.utils";
|
import { formSchema, SecretActionType, TFormSchema } from "./SecretListView.utils";
|
||||||
@@ -140,26 +141,24 @@ export const SecretDetailSidebar = ({
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const cannotReadSecretValue = permission.cannot(
|
const cannotReadSecretValue = !secretsPermissionCan(
|
||||||
|
permission,
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
{
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
secretName: secretKey,
|
secretName: secretKey,
|
||||||
secretTags: selectTagSlugs
|
secretTags: selectTagSlugs
|
||||||
})
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const isReadOnly =
|
const isReadOnly =
|
||||||
permission.can(
|
secretsPermissionCan(permission, ProjectPermissionSecretActions.DescribeSecret, {
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
environment,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath,
|
||||||
environment,
|
secretName: secretKey,
|
||||||
secretPath,
|
secretTags: selectTagSlugs
|
||||||
secretName: secretKey,
|
}) &&
|
||||||
secretTags: selectTagSlugs
|
|
||||||
})
|
|
||||||
) &&
|
|
||||||
cannotEditSecret &&
|
cannotEditSecret &&
|
||||||
cannotReadSecretValue;
|
cannotReadSecretValue;
|
||||||
|
|
||||||
|
|||||||
+7
-9
@@ -47,6 +47,7 @@ import {
|
|||||||
|
|
||||||
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
|
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
|
||||||
import { Blur } from "@app/components/v2/Blur";
|
import { Blur } from "@app/components/v2/Blur";
|
||||||
|
import { secretsPermissionCan } from "@app/lib/fn/permission";
|
||||||
import {
|
import {
|
||||||
FontAwesomeSpriteName,
|
FontAwesomeSpriteName,
|
||||||
formSchema,
|
formSchema,
|
||||||
@@ -131,15 +132,12 @@ export const SecretItem = memo(
|
|||||||
});
|
});
|
||||||
|
|
||||||
const isReadOnly =
|
const isReadOnly =
|
||||||
permission.can(
|
secretsPermissionCan(permission, ProjectPermissionSecretActions.DescribeSecret, {
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
environment,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
secretPath,
|
||||||
environment,
|
secretName,
|
||||||
secretPath,
|
secretTags: selectedTagSlugs
|
||||||
secretName,
|
}) &&
|
||||||
secretTags: selectedTagSlugs
|
|
||||||
})
|
|
||||||
) &&
|
|
||||||
permission.cannot(
|
permission.cannot(
|
||||||
ProjectPermissionSecretActions.Edit,
|
ProjectPermissionSecretActions.Edit,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
|||||||
Reference in New Issue
Block a user