fix(view-secret-value): backwards compatibility for read

This commit is contained in:
Daniel Hougaard
2025-03-11 00:08:59 +04:00
parent d74197aeb4
commit f1ba64aa66
24 changed files with 708 additions and 882 deletions
@@ -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 { ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
import { BadRequestError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
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) {
if (!actorAuthMethod) return false;
@@ -18,7 +18,8 @@ export enum ProjectPermissionActions {
}
export enum ProjectPermissionSecretActions {
DescribeSecret = "read",
DescribeAndReadValue = "read",
DescribeSecret = "describeSecret",
ReadValue = "readValue",
Create = "create",
Edit = "edit",
@@ -564,6 +565,7 @@ const buildAdminPermissionRules = () => {
can(
[
// not adding DescribeAndReadValue, because it's already covered by DescribeSecret and ReadValue
ProjectPermissionSecretActions.DescribeSecret,
ProjectPermissionSecretActions.ReadValue,
ProjectPermissionSecretActions.Create,
@@ -632,6 +634,7 @@ const buildMemberPermissionRules = () => {
can(
[
// not adding DescribeAndReadValue, because it's already covered by DescribeSecret and ReadValue
ProjectPermissionSecretActions.DescribeSecret,
ProjectPermissionSecretActions.ReadValue,
ProjectPermissionSecretActions.Edit,
@@ -808,6 +811,7 @@ export const projectMemberPermissions = buildMemberPermissionRules();
const buildViewerPermissionRules = () => {
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.ReadValue, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretFolders);
@@ -852,7 +856,6 @@ export const buildServiceTokenProjectPermission = (
) => {
const canWrite = permission.includes("write");
const canRead = permission.includes("read");
const canReadValue = permission.includes("readValue");
const { can, build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
scopes.forEach(({ secretPath, environment }) => {
@@ -876,7 +879,7 @@ export const buildServiceTokenProjectPermission = (
environment
});
}
if (canRead) {
if (canRead && subject !== ProjectPermissionSub.Secrets) {
can(ProjectPermissionActions.Read, subject, {
// @ts-expect-error type
secretPath: { $glob: secretPath },
@@ -884,12 +887,18 @@ export const buildServiceTokenProjectPermission = (
});
}
if (subject === ProjectPermissionSub.Secrets && canReadValue) {
if (subject === ProjectPermissionSub.Secrets && canRead) {
// @ts-expect-error type
can(ProjectPermissionSecretActions.ReadValue, subject as ProjectPermissionSub.Secrets, {
secretPath: { $glob: secretPath },
environment
});
// @ts-expect-error type
can(ProjectPermissionSecretActions.DescribeSecret, subject as ProjectPermissionSub.Secrets, {
secretPath: { $glob: secretPath },
environment
});
}
}
);