Merge branch 'main' into fix/accessTreeImprovements

This commit is contained in:
carlosmonastyrski
2025-03-31 18:11:47 -03:00
150 changed files with 4287 additions and 1171 deletions
@@ -7,9 +7,11 @@ import { z } from "zod";
import { ActionProjectType, ProjectType, TCertificateAuthorities, TCertificateTemplates } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { extractX509CertFromChain } from "@app/lib/certificates/extract-certificate";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
import { isFQDN } from "@app/lib/validator/validate-url";
import { TCertificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal";
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
@@ -58,7 +60,6 @@ import {
TSignIntermediateDTO,
TUpdateCaDTO
} from "./certificate-authority-types";
import { hostnameRegex } from "./certificate-authority-validators";
type TCertificateAuthorityServiceFactoryDep = {
certificateAuthorityDAL: Pick<
@@ -1017,9 +1018,7 @@ export const certificateAuthorityServiceFactory = ({
const maxPathLength = certObj.getExtension(x509.BasicConstraintsExtension)?.pathLength;
// validate imported certificate and certificate chain
const certificates = certificateChain
.match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g)
?.map((cert) => new x509.X509Certificate(cert));
const certificates = extractX509CertFromChain(certificateChain)?.map((cert) => new x509.X509Certificate(cert));
if (!certificates) throw new BadRequestError({ message: "Failed to parse certificate chain" });
@@ -1325,7 +1324,7 @@ export const certificateAuthorityServiceFactory = ({
}
// check if the altName is a valid hostname
if (hostnameRegex.test(altName)) {
if (isFQDN(altName, { allow_wildcard: true })) {
return {
type: "dns",
value: altName
@@ -1702,7 +1701,7 @@ export const certificateAuthorityServiceFactory = ({
}
// check if the altName is a valid hostname
if (hostnameRegex.test(altName)) {
if (isFQDN(altName, { allow_wildcard: true })) {
return {
type: "dns",
value: altName
@@ -1,6 +1,7 @@
import { z } from "zod";
import { isValidIp } from "@app/lib/ip";
import { isFQDN } from "@app/lib/validator/validate-url";
const isValidDate = (dateString: string) => {
const date = new Date(dateString);
@@ -9,7 +10,6 @@ const isValidDate = (dateString: string) => {
export const validateCaDateField = z.string().trim().refine(isValidDate, { message: "Invalid date format" });
export const hostnameRegex = /^(?!:\/\/)(\*\.)?([a-zA-Z0-9-_]{1,63}\.?)+(?!:\/\/)([a-zA-Z]{2,63})$/;
export const validateAltNamesField = z
.string()
.trim()
@@ -27,7 +27,7 @@ export const validateAltNamesField = z
if (data === "") return true;
// Split and validate each alt name
return data.split(", ").every((name) => {
return hostnameRegex.test(name) || z.string().email().safeParse(name).success || isValidIp(name);
return isFQDN(name, { allow_wildcard: true }) || z.string().email().safeParse(name).success || isValidIp(name);
});
},
{
@@ -11,6 +11,7 @@ export const validateCertificateDetailsAgainstTemplate = (
},
template: TCertificateTemplates
) => {
// these are validated in router using validateTemplateRegexField
const commonNameRegex = new RegExp(template.commonName);
if (!commonNameRegex.test(cert.commonName)) {
throw new BadRequestError({
@@ -6,6 +6,7 @@ import { ActionProjectType, TCertificateTemplateEstConfigsUpdate } from "@app/db
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { extractX509CertFromChain } from "@app/lib/certificates/extract-certificate";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
@@ -281,9 +282,7 @@ export const certificateTemplateServiceFactory = ({
});
// validate CA chain
const certificates = caChain
.match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g)
?.map((cert) => new x509.X509Certificate(cert));
const certificates = extractX509CertFromChain(caChain)?.map((cert) => new x509.X509Certificate(cert));
if (!certificates) {
throw new BadRequestError({ message: "Failed to parse certificate chain" });
@@ -379,9 +378,7 @@ export const certificateTemplateServiceFactory = ({
};
if (caChain) {
const certificates = caChain
.match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g)
?.map((cert) => new x509.X509Certificate(cert));
const certificates = extractX509CertFromChain(caChain)?.map((cert) => new x509.X509Certificate(cert));
if (!certificates) {
throw new BadRequestError({ message: "Failed to parse certificate chain" });
@@ -1,13 +1,27 @@
import safe from "safe-regex";
import z from "zod";
import { CharacterType, characterValidator } from "@app/lib/validator/validate-string";
export const validateTemplateRegexField = z
.string()
.min(1)
.max(100)
.regex(/^[a-zA-Z0-9 *@\-\\.\\]+$/, {
message: "Invalid pattern: only alphanumeric characters, spaces, *, ., @, -, and \\ are allowed."
})
.refine(
(val) =>
characterValidator([
CharacterType.AlphaNumeric,
CharacterType.Spaces, // (space)
CharacterType.Asterisk, // *
CharacterType.At, // @
CharacterType.Hyphen, // -
CharacterType.Period, // .
CharacterType.Backslash // \
])(val),
{
message: "Invalid pattern: only alphanumeric characters, spaces, *, ., @, -, and \\ are allowed."
}
)
// we ensure that the inputted pattern is computationally safe by limiting star height to 1
.refine((v) => safe(v), {
message: "Unsafe REGEX pattern"
@@ -1,12 +1,15 @@
import { ForbiddenError } from "@casl/ability";
import { ActionProjectType, ProjectMembershipRole, SecretKeyEncoding, TGroups } from "@app/db/schemas";
import {
constructPermissionErrorMessage,
validatePrivilegeChangeOperation
} from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
import { ProjectPermissionGroupActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { decryptAsymmetric, encryptAsymmetric } from "@app/lib/crypto";
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn";
import { ms } from "@app/lib/ms";
import { isUuidV4 } from "@app/lib/validator";
@@ -70,7 +73,7 @@ export const groupProjectServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Failed to find project with ID ${projectId}` });
if (project.version < 2) throw new BadRequestError({ message: `Failed to add group to E2EE project` });
const { permission } = await permissionService.getProjectPermission({
const { permission, membership } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
@@ -78,7 +81,7 @@ export const groupProjectServiceFactory = ({
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Groups);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionGroupActions.Create, ProjectPermissionSub.Groups);
let group: TGroups | null = null;
if (isUuidV4(groupIdOrName)) {
@@ -102,11 +105,21 @@ export const groupProjectServiceFactory = ({
project.id
);
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionGroupActions.GrantPrivileges,
ProjectPermissionSub.Groups,
permission,
rolePermission
);
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to assign group to a more privileged role",
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to assign group to role",
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionGroupActions.GrantPrivileges,
ProjectPermissionSub.Groups
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
}
@@ -248,7 +261,7 @@ export const groupProjectServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Failed to find project with ID ${projectId}` });
const { permission } = await permissionService.getProjectPermission({
const { permission, membership } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
@@ -256,7 +269,7 @@ export const groupProjectServiceFactory = ({
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Groups);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionGroupActions.Edit, ProjectPermissionSub.Groups);
const group = await groupDAL.findOne({ orgId: actorOrgId, id: groupId });
if (!group) throw new NotFoundError({ message: `Failed to find group with ID ${groupId}` });
@@ -269,11 +282,21 @@ export const groupProjectServiceFactory = ({
requestedRoleChange,
project.id
);
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionGroupActions.GrantPrivileges,
ProjectPermissionSub.Groups,
permission,
rolePermission
);
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to assign group to a more privileged role",
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to assign group to role",
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionGroupActions.GrantPrivileges,
ProjectPermissionSub.Groups
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
}
@@ -360,7 +383,7 @@ export const groupProjectServiceFactory = ({
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Groups);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionGroupActions.Delete, ProjectPermissionSub.Groups);
const deletedProjectGroup = await groupProjectDAL.transaction(async (tx) => {
const groupMembers = await userGroupMembershipDAL.findGroupMembersNotInProject(group.id, project.id, tx);
@@ -405,7 +428,7 @@ export const groupProjectServiceFactory = ({
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Groups);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionGroupActions.Read, ProjectPermissionSub.Groups);
const groupMemberships = await groupProjectDAL.findByProjectId(project.id);
return groupMemberships;
@@ -433,7 +456,7 @@ export const groupProjectServiceFactory = ({
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Groups);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionGroupActions.Read, ProjectPermissionSub.Groups);
const [groupMembership] = await groupProjectDAL.findByProjectId(project.id, {
groupId
@@ -5,11 +5,14 @@ import jwt from "jsonwebtoken";
import { IdentityAuthMethod } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import {
constructPermissionErrorMessage,
validatePrivilegeChangeOperation
} from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { ActorType, AuthTokenType } from "../auth/auth-type";
@@ -39,6 +42,31 @@ type TIdentityAwsAuthServiceFactoryDep = {
export type TIdentityAwsAuthServiceFactory = ReturnType<typeof identityAwsAuthServiceFactory>;
const awsRegionFromHeader = (authorizationHeader: string): string | null => {
// https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-auth-using-authorization-header.html
// The Authorization header takes the following form.
// Authorization: AWS4-HMAC-SHA256
// Credential=AKIAIOSFODNN7EXAMPLE/20230719/us-east-1/sts/aws4_request,
// SignedHeaders=content-length;content-type;host;x-amz-date,
// Signature=fe5f80f77d5fa3beca038a248ff027d0445342fe2855ddc963176630326f1024
//
// The credential is in the form of "<your-access-key-id>/<date>/<aws-region>/<aws-service>/aws4_request"
try {
const fields = authorizationHeader.split(" ");
for (const field of fields) {
if (field.startsWith("Credential=")) {
const parts = field.split("/");
if (parts.length >= 3) {
return parts[2];
}
}
}
} catch {
return null;
}
return null;
};
export const identityAwsAuthServiceFactory = ({
identityAccessTokenDAL,
identityAwsAuthDAL,
@@ -57,6 +85,9 @@ export const identityAwsAuthServiceFactory = ({
const headers: TAwsGetCallerIdentityHeaders = JSON.parse(Buffer.from(iamRequestHeaders, "base64").toString());
const body: string = Buffer.from(iamRequestBody, "base64").toString();
const region = headers.Authorization ? awsRegionFromHeader(headers.Authorization) : null;
const url = region ? `https://sts.${region}.amazonaws.com` : identityAwsAuth.stsEndpoint;
const {
data: {
GetCallerIdentityResponse: {
@@ -65,7 +96,7 @@ export const identityAwsAuthServiceFactory = ({
}
}: { data: TGetCallerIdentityResponse } = await axios({
method: iamHttpRequestMethod,
url: headers?.Host ? `https://${headers.Host}` : identityAwsAuth.stsEndpoint,
url,
headers,
data: body
});
@@ -93,7 +124,8 @@ export const identityAwsAuthServiceFactory = ({
.some((principalArn) => {
// convert wildcard ARN to a regular expression: "arn:aws:iam::123456789012:*" -> "^arn:aws:iam::123456789012:.*$"
// considers exact matches + wildcard matches
const regex = new RegExp(`^${principalArn.replace(/\*/g, ".*")}$`);
// heavily validated in router
const regex = new RegExp(`^${principalArn.replaceAll("*", ".*")}$`);
return regex.test(extractPrincipalArn(Arn));
});
@@ -175,7 +207,7 @@ export const identityAwsAuthServiceFactory = ({
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => {
@@ -254,7 +286,7 @@ export const identityAwsAuthServiceFactory = ({
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => {
@@ -308,7 +340,7 @@ export const identityAwsAuthServiceFactory = ({
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
return { ...awsIdentityAuth, orgId: identityMembershipOrg.orgId };
};
@@ -326,14 +358,14 @@ export const identityAwsAuthServiceFactory = ({
message: "The identity does not have aws auth"
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission, membership } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
@@ -343,11 +375,22 @@ export const identityAwsAuthServiceFactory = ({
actorOrgId
);
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.RevokeAuth,
OrgPermissionSubjects.Identity,
permission,
rolePermission
);
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to revoke aws auth of identity with more privileged role",
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to revoke aws auth of identity with more privileged role",
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.RevokeAuth,
OrgPermissionSubjects.Identity
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
@@ -1,6 +1,8 @@
import safe from "safe-regex";
import { z } from "zod";
const twelveDigitRegex = /^\d{12}$/;
// akhilmhdh: change this to a normal function later. Checked no redosable at the moment
const arnRegex = /^arn:aws:iam::\d{12}:(user\/[a-zA-Z0-9_.@+*/-]+|role\/[a-zA-Z0-9_.@+*/-]+|\*)$/;
export const validateAccountIds = z
@@ -42,7 +44,8 @@ export const validatePrincipalArns = z
// Split the string by commas to check each supposed ARN
const arns = data.split(",");
// Return true only if every item matches one of the allowed ARN formats
return arns.every((arn) => arnRegex.test(arn.trim()));
// and checks whether the provided regex is safe
return arns.map((el) => el.trim()).every((arn) => safe(`^${arn.replaceAll("*", ".*")}$`) && arnRegex.test(arn));
},
{
message:
@@ -3,11 +3,14 @@ import jwt from "jsonwebtoken";
import { IdentityAuthMethod } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import {
constructPermissionErrorMessage,
validatePrivilegeChangeOperation
} from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { ActorType, AuthTokenType } from "../auth/auth-type";
@@ -147,7 +150,7 @@ export const identityAzureAuthServiceFactory = ({
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => {
@@ -225,7 +228,7 @@ export const identityAzureAuthServiceFactory = ({
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => {
@@ -281,7 +284,7 @@ export const identityAzureAuthServiceFactory = ({
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
return { ...identityAzureAuth, orgId: identityMembershipOrg.orgId };
};
@@ -300,14 +303,14 @@ export const identityAzureAuthServiceFactory = ({
message: "The identity does not have azure auth"
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission, membership } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
@@ -316,11 +319,21 @@ export const identityAzureAuthServiceFactory = ({
actorAuthMethod,
actorOrgId
);
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.RevokeAuth,
OrgPermissionSubjects.Identity,
permission,
rolePermission
);
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to revoke azure auth of identity with more privileged role",
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to revoke azure auth of identity with more privileged role",
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.RevokeAuth,
OrgPermissionSubjects.Identity
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
@@ -3,11 +3,14 @@ import jwt from "jsonwebtoken";
import { IdentityAuthMethod } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import {
constructPermissionErrorMessage,
validatePrivilegeChangeOperation
} from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { ActorType, AuthTokenType } from "../auth/auth-type";
@@ -188,7 +191,7 @@ export const identityGcpAuthServiceFactory = ({
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => {
@@ -268,7 +271,7 @@ export const identityGcpAuthServiceFactory = ({
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => {
@@ -326,7 +329,7 @@ export const identityGcpAuthServiceFactory = ({
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
return { ...identityGcpAuth, orgId: identityMembershipOrg.orgId };
};
@@ -346,14 +349,14 @@ export const identityGcpAuthServiceFactory = ({
message: "The identity does not have gcp auth"
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission, membership } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
@@ -362,11 +365,21 @@ export const identityGcpAuthServiceFactory = ({
actorAuthMethod,
actorOrgId
);
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.RevokeAuth,
OrgPermissionSubjects.Identity,
permission,
rolePermission
);
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to revoke gcp auth of identity with more privileged role",
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to revoke gcp auth of identity with more privileged role",
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.RevokeAuth,
OrgPermissionSubjects.Identity
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
@@ -5,11 +5,20 @@ import { JwksClient } from "jwks-rsa";
import { IdentityAuthMethod, TIdentityJwtAuthsUpdate } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import {
constructPermissionErrorMessage,
validatePrivilegeChangeOperation
} from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
import {
BadRequestError,
ForbiddenRequestError,
NotFoundError,
PermissionBoundaryError,
UnauthorizedError
} from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { getStringValueByDot } from "@app/lib/template/dot-access";
@@ -278,7 +287,7 @@ export const identityJwtAuthServiceFactory = ({
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => {
@@ -381,7 +390,7 @@ export const identityJwtAuthServiceFactory = ({
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => {
@@ -470,7 +479,7 @@ export const identityJwtAuthServiceFactory = ({
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
const identityJwtAuth = await identityJwtAuthDAL.findOne({ identityId });
@@ -504,7 +513,7 @@ export const identityJwtAuthServiceFactory = ({
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission, membership } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
@@ -512,7 +521,7 @@ export const identityJwtAuthServiceFactory = ({
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
@@ -522,11 +531,21 @@ export const identityJwtAuthServiceFactory = ({
actorOrgId
);
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.RevokeAuth,
OrgPermissionSubjects.Identity,
permission,
rolePermission
);
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to revoke jwt auth of identity with more privileged role",
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to revoke jwt auth of identity with more privileged role",
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.RevokeAuth,
OrgPermissionSubjects.Identity
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
@@ -5,11 +5,14 @@ import jwt from "jsonwebtoken";
import { IdentityAuthMethod, TIdentityKubernetesAuthsUpdate } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import {
constructPermissionErrorMessage,
validatePrivilegeChangeOperation
} from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { ActorType, AuthTokenType } from "../auth/auth-type";
@@ -257,7 +260,7 @@ export const identityKubernetesAuthServiceFactory = ({
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => {
@@ -350,7 +353,7 @@ export const identityKubernetesAuthServiceFactory = ({
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => {
@@ -446,7 +449,7 @@ export const identityKubernetesAuthServiceFactory = ({
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
const { decryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization,
@@ -483,14 +486,14 @@ export const identityKubernetesAuthServiceFactory = ({
message: "The identity does not have kubernetes auth"
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission, membership } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
@@ -499,11 +502,21 @@ export const identityKubernetesAuthServiceFactory = ({
actorAuthMethod,
actorOrgId
);
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.RevokeAuth,
OrgPermissionSubjects.Identity,
permission,
rolePermission
);
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to revoke kubernetes auth of identity with more privileged role",
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to revoke kubernetes auth of identity with more privileged role",
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.RevokeAuth,
OrgPermissionSubjects.Identity
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
@@ -6,11 +6,20 @@ import { JwksClient } from "jwks-rsa";
import { IdentityAuthMethod, TIdentityOidcAuthsUpdate } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import {
constructPermissionErrorMessage,
validatePrivilegeChangeOperation
} from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
import {
BadRequestError,
ForbiddenRequestError,
NotFoundError,
PermissionBoundaryError,
UnauthorizedError
} from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { getStringValueByDot } from "@app/lib/template/dot-access";
@@ -204,7 +213,7 @@ export const identityOidcAuthServiceFactory = ({
}
);
return { accessToken, identityOidcAuth, identityAccessToken, identityMembershipOrg };
return { accessToken, identityOidcAuth, identityAccessToken, identityMembershipOrg, oidcTokenData: tokenData };
};
const attachOidcAuth = async ({
@@ -249,7 +258,7 @@ export const identityOidcAuthServiceFactory = ({
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => {
@@ -341,7 +350,7 @@ export const identityOidcAuthServiceFactory = ({
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => {
@@ -414,7 +423,7 @@ export const identityOidcAuthServiceFactory = ({
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
const identityOidcAuth = await identityOidcAuthDAL.findOne({ identityId });
@@ -442,7 +451,7 @@ export const identityOidcAuthServiceFactory = ({
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission, membership } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
@@ -450,7 +459,7 @@ export const identityOidcAuthServiceFactory = ({
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
@@ -460,11 +469,22 @@ export const identityOidcAuthServiceFactory = ({
actorOrgId
);
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.RevokeAuth,
OrgPermissionSubjects.Identity,
permission,
rolePermission
);
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to revoke oidc auth of identity with more privileged role",
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to revoke oidc auth of identity with more privileged role",
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.RevokeAuth,
OrgPermissionSubjects.Identity
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
@@ -1,14 +1,16 @@
import { ForbiddenError, subject } from "@casl/ability";
import { ActionProjectType, ProjectMembershipRole } from "@app/db/schemas";
import {
constructPermissionErrorMessage,
validatePrivilegeChangeOperation
} from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { ProjectPermissionIdentityActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn";
import { ms } from "@app/lib/ms";
import { ActorType } from "../auth/auth-type";
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
import { TProjectDALFactory } from "../project/project-dal";
import { ProjectUserMembershipTemporaryMode } from "../project-membership/project-membership-types";
@@ -54,7 +56,7 @@ export const identityProjectServiceFactory = ({
projectId,
roles
}: TCreateProjectIdentityDTO) => {
const { permission } = await permissionService.getProjectPermission({
const { permission, membership } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
@@ -63,7 +65,7 @@ export const identityProjectServiceFactory = ({
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionIdentityActions.Create,
subject(ProjectPermissionSub.Identity, {
identityId
})
@@ -91,11 +93,21 @@ export const identityProjectServiceFactory = ({
projectId
);
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionIdentityActions.GrantPrivileges,
ProjectPermissionSub.Identity,
permission,
rolePermission
);
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to assign to a more privileged role",
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to assign to role",
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionIdentityActions.GrantPrivileges,
ProjectPermissionSub.Identity
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
}
@@ -162,7 +174,7 @@ export const identityProjectServiceFactory = ({
actorAuthMethod,
actorOrgId
}: TUpdateProjectIdentityDTO) => {
const { permission } = await permissionService.getProjectPermission({
const { permission, membership } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
@@ -171,7 +183,7 @@ export const identityProjectServiceFactory = ({
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionIdentityActions.Edit,
subject(ProjectPermissionSub.Identity, { identityId })
);
@@ -187,11 +199,22 @@ export const identityProjectServiceFactory = ({
projectId
);
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionIdentityActions.GrantPrivileges,
ProjectPermissionSub.Identity,
permission,
rolePermission
);
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to change to a more privileged role",
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to change role",
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionIdentityActions.GrantPrivileges,
ProjectPermissionSub.Identity
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
}
@@ -271,26 +294,10 @@ export const identityProjectServiceFactory = ({
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionIdentityActions.Delete,
subject(ProjectPermissionSub.Identity, { identityId })
);
const { permission: identityRolePermission } = await permissionService.getProjectPermission({
actor: ActorType.IDENTITY,
actorId: identityId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
const permissionBoundary = validatePermissionBoundary(permission, identityRolePermission);
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to remove more privileged identity",
details: { missingPermissions: permissionBoundary.missingPermissions }
});
const [deletedIdentity] = await identityProjectDAL.delete({ identityId, projectId });
return deletedIdentity;
};
@@ -315,7 +322,10 @@ export const identityProjectServiceFactory = ({
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Identity);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Read,
ProjectPermissionSub.Identity
);
const identityMemberships = await identityProjectDAL.findByProjectId(projectId, {
limit,
@@ -348,7 +358,7 @@ export const identityProjectServiceFactory = ({
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionIdentityActions.Read,
subject(ProjectPermissionSub.Identity, { identityId })
);
@@ -3,11 +3,14 @@ import jwt from "jsonwebtoken";
import { IdentityAuthMethod, TableName } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import {
constructPermissionErrorMessage,
validatePrivilegeChangeOperation
} from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { ActorType, AuthTokenType } from "../auth/auth-type";
@@ -85,7 +88,7 @@ export const identityTokenAuthServiceFactory = ({
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => {
@@ -161,7 +164,7 @@ export const identityTokenAuthServiceFactory = ({
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => {
@@ -215,7 +218,7 @@ export const identityTokenAuthServiceFactory = ({
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
return { ...identityTokenAuth, orgId: identityMembershipOrg.orgId };
};
@@ -245,9 +248,9 @@ export const identityTokenAuthServiceFactory = ({
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
const { permission: rolePermission, membership } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identityId,
identityMembershipOrg.orgId,
@@ -255,11 +258,21 @@ export const identityTokenAuthServiceFactory = ({
actorOrgId
);
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.RevokeAuth,
OrgPermissionSubjects.Identity,
permission,
rolePermission
);
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to revoke token auth of identity with more privileged role",
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to revoke token auth of identity with more privileged role",
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.RevokeAuth,
OrgPermissionSubjects.Identity
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
@@ -301,20 +314,31 @@ export const identityTokenAuthServiceFactory = ({
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
const { permission: rolePermission, membership } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identityId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.CreateToken,
OrgPermissionSubjects.Identity,
permission,
rolePermission
);
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to create token for identity with more privileged role",
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to create token for identity with more privileged role",
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.CreateToken,
OrgPermissionSubjects.Identity
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
@@ -383,7 +407,7 @@ export const identityTokenAuthServiceFactory = ({
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
const tokens = await identityAccessTokenDAL.find(
{
@@ -429,20 +453,30 @@ export const identityTokenAuthServiceFactory = ({
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
const { permission: rolePermission, membership } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identityId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.CreateToken,
OrgPermissionSubjects.Identity,
permission,
rolePermission
);
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to update token for identity with more privileged role",
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to update token for identity with more privileged role",
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.CreateToken,
OrgPermissionSubjects.Identity
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
@@ -496,7 +530,7 @@ export const identityTokenAuthServiceFactory = ({
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const [revokedToken] = await identityAccessTokenDAL.update(
{
@@ -6,11 +6,14 @@ import jwt from "jsonwebtoken";
import { IdentityAuthMethod } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import {
constructPermissionErrorMessage,
validatePrivilegeChangeOperation
} from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors";
import { checkIPAgainstBlocklist, extractIPDetails, isValidIpOrCidr, TIp } from "@app/lib/ip";
import { ActorType, AuthTokenType } from "../auth/auth-type";
@@ -184,7 +187,7 @@ export const identityUaServiceFactory = ({
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
const reformattedClientSecretTrustedIps = clientSecretTrustedIps.map((clientSecretTrustedIp) => {
@@ -278,7 +281,7 @@ export const identityUaServiceFactory = ({
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
const reformattedClientSecretTrustedIps = clientSecretTrustedIps?.map((clientSecretTrustedIp) => {
@@ -350,7 +353,7 @@ export const identityUaServiceFactory = ({
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
return { ...uaIdentityAuth, orgId: identityMembershipOrg.orgId };
};
@@ -376,20 +379,30 @@ export const identityUaServiceFactory = ({
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
const { permission: rolePermission, membership } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identityId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.RevokeAuth,
OrgPermissionSubjects.Identity,
permission,
rolePermission
);
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to revoke universal auth of identity with more privileged role",
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to revoke universal auth of identity with more privileged role",
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.RevokeAuth,
OrgPermissionSubjects.Identity
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
@@ -419,14 +432,14 @@ export const identityUaServiceFactory = ({
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission, membership } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
@@ -435,11 +448,21 @@ export const identityUaServiceFactory = ({
actorAuthMethod,
actorOrgId
);
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.CreateToken,
OrgPermissionSubjects.Identity,
permission,
rolePermission
);
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to create client secret for a more privileged identity.",
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to create client secret for identity.",
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.CreateToken,
OrgPermissionSubjects.Identity
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
@@ -448,6 +471,7 @@ export const identityUaServiceFactory = ({
const clientSecretHash = await bcrypt.hash(clientSecret, appCfg.SALT_ROUNDS);
const identityUaAuth = await identityUaDAL.findOne({ identityId: identityMembershipOrg.identityId });
if (!identityUaAuth) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
const identityUaClientSecret = await identityUaClientSecretDAL.create({
identityUAId: identityUaAuth.id,
@@ -481,14 +505,14 @@ export const identityUaServiceFactory = ({
message: "The identity does not have universal auth"
});
}
const { permission } = await permissionService.getOrgPermission(
const { permission, membership } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
@@ -498,11 +522,21 @@ export const identityUaServiceFactory = ({
actorOrgId
);
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.GetToken,
OrgPermissionSubjects.Identity,
permission,
rolePermission
);
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to get identity client secret with more privileged role",
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to get identity client secret with more privileged role",
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.GetToken,
OrgPermissionSubjects.Identity
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
@@ -534,14 +568,20 @@ export const identityUaServiceFactory = ({
});
}
const { permission } = await permissionService.getOrgPermission(
const identityUa = await identityUaDAL.findOne({ identityId });
if (!identityUa) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
const clientSecret = await identityUaClientSecretDAL.findOne({ id: clientSecretId, identityUAId: identityUa.id });
if (!clientSecret) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
const { permission, membership } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
@@ -550,15 +590,24 @@ export const identityUaServiceFactory = ({
actorAuthMethod,
actorOrgId
);
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.GetToken,
OrgPermissionSubjects.Identity,
permission,
rolePermission
);
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to read identity client secret of identity with more privileged role",
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to read identity client secret of identity with more privileged role",
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.GetToken,
OrgPermissionSubjects.Identity
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
const clientSecret = await identityUaClientSecretDAL.findById(clientSecretId);
return { ...clientSecret, identityId, orgId: identityMembershipOrg.orgId };
};
@@ -579,14 +628,20 @@ export const identityUaServiceFactory = ({
});
}
const { permission } = await permissionService.getOrgPermission(
const identityUa = await identityUaDAL.findOne({ identityId });
if (!identityUa) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
const clientSecret = await identityUaClientSecretDAL.findOne({ id: clientSecretId, identityUAId: identityUa.id });
if (!clientSecret) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
const { permission, membership } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Delete, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
@@ -595,18 +650,31 @@ export const identityUaServiceFactory = ({
actorAuthMethod,
actorOrgId
);
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to revoke identity client secret with more privileged role",
const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.DeleteToken,
OrgPermissionSubjects.Identity,
permission,
rolePermission
);
if (!permissionBoundary.isValid) {
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to revoke identity client secret with more privileged role",
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.DeleteToken,
OrgPermissionSubjects.Identity
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
}
const clientSecret = await identityUaClientSecretDAL.updateById(clientSecretId, {
const updatedClientSecret = await identityUaClientSecretDAL.updateById(clientSecretId, {
isClientSecretRevoked: true
});
return { ...clientSecret, identityId, orgId: identityMembershipOrg.orgId };
return { ...updatedClientSecret, identityId, orgId: identityMembershipOrg.orgId };
};
return {
@@ -2,13 +2,15 @@ import { ForbiddenError } from "@casl/ability";
import { OrgMembershipRole, TableName, TOrgRoles } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import {
constructPermissionErrorMessage,
validatePrivilegeChangeOperation
} from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
import { ActorType } from "../auth/auth-type";
import { validateIdentityUpdateForSuperAdminPrivileges } from "../super-admin/super-admin-fns";
import { TIdentityDALFactory } from "./identity-dal";
import { TIdentityMetadataDALFactory } from "./identity-metadata-dal";
@@ -51,19 +53,35 @@ export const identityServiceFactory = ({
actorOrgId,
metadata
}: TCreateIdentityDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity);
const { permission, membership } = await permissionService.getOrgPermission(
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const { permission: rolePermission, role: customRole } = await permissionService.getOrgPermissionByRole(
role,
orgId
);
const isCustomRole = Boolean(customRole);
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.GrantPrivileges,
OrgPermissionSubjects.Identity,
permission,
rolePermission
);
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to create a more privileged identity",
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to create identity",
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.GrantPrivileges,
OrgPermissionSubjects.Identity
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
@@ -121,29 +139,14 @@ export const identityServiceFactory = ({
const identityOrgMembership = await identityOrgMembershipDAL.findOne({ identityId: id });
if (!identityOrgMembership) throw new NotFoundError({ message: `Failed to find identity with id ${id}` });
const { permission } = await permissionService.getOrgPermission(
const { permission, membership } = await permissionService.getOrgPermission(
actor,
actorId,
identityOrgMembership.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
const { permission: identityRolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
id,
identityOrgMembership.orgId,
actorAuthMethod,
actorOrgId
);
const permissionBoundary = validatePermissionBoundary(permission, identityRolePermission);
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to update a more privileged identity",
details: { missingPermissions: permissionBoundary.missingPermissions }
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
let customRole: TOrgRoles | undefined;
if (role) {
@@ -153,11 +156,21 @@ export const identityServiceFactory = ({
);
const isCustomRole = Boolean(customOrgRole);
const appliedRolePermissionBoundary = validatePermissionBoundary(permission, rolePermission);
const appliedRolePermissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.GrantPrivileges,
OrgPermissionSubjects.Identity,
permission,
rolePermission
);
if (!appliedRolePermissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to create a more privileged identity",
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to update identity",
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.GrantPrivileges,
OrgPermissionSubjects.Identity
),
details: { missingPermissions: appliedRolePermissionBoundary.missingPermissions }
});
if (isCustomRole) customRole = customOrgRole;
@@ -209,7 +222,7 @@ export const identityServiceFactory = ({
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
return identity;
};
@@ -233,21 +246,8 @@ export const identityServiceFactory = ({
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Identity);
const { permission: identityRolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
id,
identityOrgMembership.orgId,
actorAuthMethod,
actorOrgId
);
const permissionBoundary = validatePermissionBoundary(permission, identityRolePermission);
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: "Failed to delete more privileged identity",
details: { missingPermissions: permissionBoundary.missingPermissions }
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Delete, OrgPermissionSubjects.Identity);
const deletedIdentity = await identityDAL.deleteById(id);
@@ -269,7 +269,7 @@ export const identityServiceFactory = ({
search
}: TListOrgIdentitiesByOrgIdDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
const identityMemberships = await identityOrgMembershipDAL.find({
[`${TableName.IdentityOrgMembership}.orgId` as "orgId"]: orgId,
@@ -305,7 +305,7 @@ export const identityServiceFactory = ({
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
const identityMemberships = await identityProjectDAL.findByIdentityId(identityId);
return identityMemberships;
@@ -584,7 +584,7 @@ const syncSecretsAzureKeyVault = async ({
}[] = [];
Object.keys(secrets).forEach((key) => {
const hyphenatedKey = key.replace(/_/g, "-");
const hyphenatedKey = key.replaceAll("_", "-");
if (!(hyphenatedKey in res)) {
// case: secret has been created
setSecrets.push({
@@ -603,7 +603,7 @@ const syncSecretsAzureKeyVault = async ({
const deleteSecrets: AzureKeyVaultSecret[] = [];
Object.keys(res).forEach((key) => {
const underscoredKey = key.replace(/-/g, "_");
const underscoredKey = key.replaceAll("-", "_");
if (!(underscoredKey in secrets)) {
deleteSecrets.push(res[key]);
}
@@ -617,7 +617,7 @@ const syncSecretsAzureKeyVault = async ({
if (!integration.lastUsed) {
Object.keys(res).forEach((key) => {
// first time using integration
const underscoredKey = key.replace(/-/g, "_");
const underscoredKey = key.replaceAll("-", "_");
// -> apply initial sync behavior
switch (metadata.initialSyncBehavior) {
@@ -3578,7 +3578,7 @@ const syncSecretsTeamCity = async ({
.filter((parameter) => !parameter.inherited)
.reduce(
(obj, secret) => {
const secretName = secret.name.replace(/^env\./, "");
const secretName = secret.name.startsWith(".env") ? secret.name.slice(4) : secret.name;
return {
...obj,
[secretName]: secret.value
@@ -3635,7 +3635,7 @@ const syncSecretsTeamCity = async ({
)
).data.property.reduce(
(obj, secret) => {
const secretName = secret.name.replace(/^env\./, "");
const secretName = secret.name.startsWith("env.") ? secret.name.slice(4) : secret.name;
return {
...obj,
[secretName]: secret.value
+3 -3
View File
@@ -43,7 +43,7 @@ export const orgDALFactory = (db: TDbClient) => {
.select(selectAllTableCols(TableName.Organization))
.select(
db.raw(`
CASE
CASE
WHEN ${TableName.SamlConfig}."orgId" IS NOT NULL THEN '${OrgAuthMethod.SAML}'
WHEN ${TableName.OidcConfig}."orgId" IS NOT NULL THEN '${OrgAuthMethod.OIDC}'
ELSE ''
@@ -80,7 +80,7 @@ export const orgDALFactory = (db: TDbClient) => {
.select(selectAllTableCols(TableName.Organization))
.select(
db.raw(`
CASE
CASE
WHEN ${TableName.SamlConfig}."orgId" IS NOT NULL THEN '${OrgAuthMethod.SAML}'
WHEN ${TableName.OidcConfig}."orgId" IS NOT NULL THEN '${OrgAuthMethod.OIDC}'
ELSE ''
@@ -119,7 +119,7 @@ export const orgDALFactory = (db: TDbClient) => {
.select(selectAllTableCols(TableName.Organization))
.select(
db.raw(`
CASE
CASE
WHEN ${TableName.SamlConfig}."orgId" IS NOT NULL THEN 'saml'
WHEN ${TableName.OidcConfig}."orgId" IS NOT NULL THEN 'oidc'
ELSE ''
+5 -1
View File
@@ -12,5 +12,9 @@ export const sanitizedOrganizationSchema = OrganizationsSchema.pick({
kmsDefaultKeyId: true,
defaultMembershipRole: true,
enforceMfa: true,
selectedMfaMethod: true
selectedMfaMethod: true,
allowSecretSharingOutsideOrganization: true,
shouldUseNewPrivilegeSystem: true,
privilegeUpgradeInitiatedByUsername: true,
privilegeUpgradeInitiatedAt: true
});
+88 -8
View File
@@ -21,18 +21,29 @@ import { TLicenseServiceFactory } from "@app/ee/services/license/license-service
import { TOidcConfigDALFactory } from "@app/ee/services/oidc/oidc-config-dal";
import {
OrgPermissionActions,
OrgPermissionGroupActions,
OrgPermissionSecretShareAction,
OrgPermissionSubjects
} from "@app/ee/services/permission/org-permission";
import {
constructPermissionErrorMessage,
validatePrivilegeChangeOperation
} from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { ProjectPermissionMemberActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { TProjectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-dal";
import { TSamlConfigDALFactory } from "@app/ee/services/saml-config/saml-config-dal";
import { getConfig } from "@app/lib/config/env";
import { generateAsymmetricKeyPair } from "@app/lib/crypto";
import { generateSymmetricKey, infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { generateUserSrpKeys } from "@app/lib/crypto/srp";
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
import {
BadRequestError,
ForbiddenRequestError,
NotFoundError,
PermissionBoundaryError,
UnauthorizedError
} from "@app/lib/errors";
import { groupBy } from "@app/lib/fn";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { isDisposableEmail } from "@app/lib/validator";
@@ -76,6 +87,7 @@ import {
TResendOrgMemberInvitationDTO,
TUpdateOrgDTO,
TUpdateOrgMembershipDTO,
TUpgradePrivilegeSystemDTO,
TVerifyUserToOrgDTO
} from "./org-types";
@@ -187,7 +199,7 @@ export const orgServiceFactory = ({
const getOrgGroups = async ({ actor, actorId, orgId, actorAuthMethod, actorOrgId }: TGetOrgGroupsDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Groups);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Read, OrgPermissionSubjects.Groups);
const groups = await groupDAL.findByOrgId(orgId);
return groups;
};
@@ -281,6 +293,45 @@ export const orgServiceFactory = ({
};
};
const upgradePrivilegeSystem = async ({
actorId,
actorOrgId,
actorAuthMethod,
orgId
}: TUpgradePrivilegeSystemDTO) => {
const { membership } = await permissionService.getUserOrgPermission(actorId, orgId, actorAuthMethod, actorOrgId);
if (membership.role !== OrgMembershipRole.Admin) {
throw new ForbiddenRequestError({
message: "Insufficient privileges - only the organization admin can upgrade the privilege system."
});
}
return orgDAL.transaction(async (tx) => {
const org = await orgDAL.findById(actorOrgId, tx);
if (org.shouldUseNewPrivilegeSystem) {
throw new BadRequestError({
message: "Privilege system already upgraded"
});
}
const user = await userDAL.findById(actorId, tx);
if (!user) {
throw new NotFoundError({ message: `User with ID '${actorId}' not found` });
}
return orgDAL.updateById(
actorOrgId,
{
shouldUseNewPrivilegeSystem: true,
privilegeUpgradeInitiatedAt: new Date(),
privilegeUpgradeInitiatedByUsername: user.username
},
tx
);
});
};
/*
* Update organization details
* */
@@ -856,7 +907,7 @@ export const orgServiceFactory = ({
// if there exist no project membership we set is as given by the request
for await (const project of projectsToInvite) {
const projectId = project.id;
const { permission: projectPermission } = await permissionService.getProjectPermission({
const { permission: projectPermission, membership } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
@@ -865,7 +916,7 @@ export const orgServiceFactory = ({
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(projectPermission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionMemberActions.Create,
ProjectPermissionSub.Member
);
const existingMembers = await projectMembershipDAL.find(
@@ -888,6 +939,34 @@ export const orgServiceFactory = ({
ProjectMembershipRole.Member
];
for await (const invitedRole of invitedProjectRoles) {
const { permission: rolePermission } = await permissionService.getProjectPermissionByRole(
invitedRole,
projectId
);
if (invitedRole !== ProjectMembershipRole.NoAccess) {
const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionMemberActions.GrantPrivileges,
ProjectPermissionSub.Member,
projectPermission,
rolePermission
);
if (!permissionBoundary.isValid)
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to invite user to the project",
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionMemberActions.GrantPrivileges,
ProjectPermissionSub.Member
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
}
}
const customProjectRoles = invitedProjectRoles.filter(
(role) => !Object.values(ProjectMembershipRole).includes(role as ProjectMembershipRole)
);
@@ -1021,9 +1100,9 @@ export const orgServiceFactory = ({
const sanitizedProjectMembershipRoles: TProjectUserMembershipRolesInsert[] = [];
invitedProjectRoles.forEach((projectRole) => {
const isCustomRole = Boolean(customRolesGroupBySlug?.[projectRole]?.[0]);
projectMemberships.forEach((membership) => {
projectMemberships.forEach((membershipEntry) => {
sanitizedProjectMembershipRoles.push({
projectMembershipId: membership.id,
projectMembershipId: membershipEntry.id,
role: isCustomRole ? ProjectMembershipRole.Custom : projectRole,
customRoleId: customRolesGroupBySlug[projectRole] ? customRolesGroupBySlug[projectRole][0].id : null
});
@@ -1304,6 +1383,7 @@ export const orgServiceFactory = ({
getOrgGroups,
listProjectMembershipsByOrgMembershipId,
findOrgBySlug,
resendOrgMemberInvitation
resendOrgMemberInvitation,
upgradePrivilegeSystem
};
};
+2
View File
@@ -76,6 +76,8 @@ export type TUpdateOrgDTO = {
}>;
} & TOrgPermission;
export type TUpgradePrivilegeSystemDTO = Omit<TOrgPermission, "actor">;
export type TGetOrgGroupsDTO = TOrgPermission;
export type TListProjectMembershipsByOrgMembershipIdDTO = {
@@ -2,7 +2,7 @@ import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { ProjectPermissionMemberActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError } from "@app/lib/errors";
import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal";
@@ -40,7 +40,7 @@ export const projectKeyServiceFactory = ({
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
const receiverMembership = await projectMembershipDAL.findOne({
userId: receiverId,
@@ -89,7 +89,7 @@ export const projectKeyServiceFactory = ({
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
return projectKeyDAL.findAllProjectUserPubKeys(projectId);
};
@@ -3,12 +3,15 @@ import { ForbiddenError } from "@casl/ability";
import { ActionProjectType, ProjectMembershipRole, ProjectVersion, TableName } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import {
constructPermissionErrorMessage,
validatePrivilegeChangeOperation
} from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { ProjectPermissionMemberActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { TProjectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-dal";
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { BadRequestError, ForbiddenRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn";
import { ms } from "@app/lib/ms";
@@ -86,7 +89,7 @@ export const projectMembershipServiceFactory = ({
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
const projectMembers = await projectMembershipDAL.findAllProjectMembers(projectId);
@@ -130,7 +133,7 @@ export const projectMembershipServiceFactory = ({
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
const [membership] = await projectMembershipDAL.findAllProjectMembers(projectId, { username });
if (!membership) throw new NotFoundError({ message: `Project membership not found for user '${username}'` });
@@ -153,7 +156,7 @@ export const projectMembershipServiceFactory = ({
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
const [membership] = await projectMembershipDAL.findAllProjectMembers(projectId, { id });
if (!membership) throw new NotFoundError({ message: `Project membership not found for user ${id}` });
@@ -180,7 +183,7 @@ export const projectMembershipServiceFactory = ({
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Member);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Create, ProjectPermissionSub.Member);
const orgMembers = await orgDAL.findMembership({
[`${TableName.OrgMembership}.orgId` as "orgId"]: project.orgId,
$in: {
@@ -253,7 +256,7 @@ export const projectMembershipServiceFactory = ({
membershipId,
roles
}: TUpdateProjectMembershipDTO) => {
const { permission } = await permissionService.getProjectPermission({
const { permission, membership } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
@@ -261,7 +264,7 @@ export const projectMembershipServiceFactory = ({
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
const membershipUser = await userDAL.findUserByProjectMembershipId(membershipId);
if (membershipUser?.isGhost || membershipUser?.projectId !== projectId) {
@@ -274,11 +277,21 @@ export const projectMembershipServiceFactory = ({
projectId
);
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionMemberActions.GrantPrivileges,
ProjectPermissionSub.Member,
permission,
rolePermission
);
if (!permissionBoundary.isValid)
throw new ForbiddenRequestError({
name: "PermissionBoundaryError",
message: `Failed to change to a more privileged role ${requestedRoleChange}`,
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
`Failed to change role ${requestedRoleChange}`,
membership.shouldUseNewPrivilegeSystem,
ProjectPermissionMemberActions.GrantPrivileges,
ProjectPermissionSub.Member
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
}
@@ -361,7 +374,7 @@ export const projectMembershipServiceFactory = ({
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Member);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Delete, ProjectPermissionSub.Member);
const member = await userDAL.findUserByProjectMembershipId(membershipId);
@@ -397,7 +410,7 @@ export const projectMembershipServiceFactory = ({
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Member);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Delete, ProjectPermissionSub.Member);
const project = await projectDAL.findById(projectId);
@@ -7,14 +7,16 @@ import { groupBy, removeTrailingSlash } from "@app/lib/fn";
import { ormify, selectAllTableCols } from "@app/lib/knex";
import { OrderByDirection } from "@app/lib/types";
import { isValidSecretPath } from "@app/lib/validator";
import { CharacterType, characterValidator } from "@app/lib/validator/validate-string";
import { SecretsOrderBy } from "@app/services/secret/secret-types";
import { TFindFoldersDeepByParentIdsDTO } from "./secret-folder-types";
export const validateFolderName = (folderName: string) => {
const validNameRegex = /^[a-zA-Z0-9-_]+$/;
return validNameRegex.test(folderName);
};
export const validateFolderName = characterValidator([
CharacterType.AlphaNumeric,
CharacterType.Hyphen,
CharacterType.Underscore
]);
const sqlFindMultipleFolderByEnvPathQuery = (db: Knex, query: Array<{ envId: string; secretPath: string }>) => {
// this is removing an trailing slash like /folder1/folder2/ -> /folder1/folder2
@@ -188,9 +190,9 @@ const sqlFindSecretPathByFolderId = (db: Knex, projectId: string, folderIds: str
// the root folder check is used to avoid last / and also root name in folders
depth: db.raw("parent.depth + 1"),
path: db.raw(
`CONCAT( CASE
WHEN ${TableName.SecretFolder}."parentId" is NULL THEN ''
ELSE CONCAT('/', secret_folders.name)
`CONCAT( CASE
WHEN ${TableName.SecretFolder}."parentId" is NULL THEN ''
ELSE CONCAT('/', secret_folders.name)
END, parent.path )`
),
child: db.raw("COALESCE(parent.child, parent.id)"),
@@ -464,7 +466,7 @@ export const secretFolderDALFactory = (db: TDbClient) => {
db.raw("parents.depth + 1 as depth"),
db.raw(
`CONCAT(
CASE WHEN parents.path = '/' THEN '' ELSE parents.path END,
CASE WHEN parents.path = '/' THEN '' ELSE parents.path END,
CASE WHEN ${TableName.SecretFolder}."parentId" is NULL THEN '' ELSE CONCAT('/', secret_folders.name) END
)`
),
@@ -1,6 +1,7 @@
import { z } from "zod";
import { SecretSyncs } from "@app/lib/api-docs";
import { CharacterType, characterValidator } from "@app/lib/validator/validate-string";
import { AppConnection, AWSRegion } from "@app/services/app-connection/app-connection-enums";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import {
@@ -10,6 +11,25 @@ import {
} from "@app/services/secret-sync/secret-sync-schemas";
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
const tagFieldCharacterValidator = characterValidator([
CharacterType.AlphaNumeric,
CharacterType.Spaces,
CharacterType.Period,
CharacterType.Underscore,
CharacterType.Colon,
CharacterType.ForwardSlash,
CharacterType.Equals,
CharacterType.Plus,
CharacterType.Hyphen,
CharacterType.At
]);
const pathCharacterValidator = characterValidator([
CharacterType.AlphaNumeric,
CharacterType.Underscore,
CharacterType.Hyphen
]);
const AwsParameterStoreSyncDestinationConfigSchema = z.object({
region: z.nativeEnum(AWSRegion).describe(SecretSyncs.DESTINATION_CONFIG.AWS_PARAMETER_STORE.region),
path: z
@@ -17,35 +37,54 @@ const AwsParameterStoreSyncDestinationConfigSchema = z.object({
.trim()
.min(1, "Parameter Store Path required")
.max(2048, "Cannot exceed 2048 characters")
.regex(/^\/([/]|(([\w-]+\/)+))?$/, 'Invalid path - must follow "/example/path/" format')
.refine(
(val) =>
val.startsWith("/") &&
val.endsWith("/") &&
val
.split("/")
.filter(Boolean)
.every((el) => pathCharacterValidator(el)),
'Invalid path - must follow "/example/path/" format'
)
.describe(SecretSyncs.DESTINATION_CONFIG.AWS_PARAMETER_STORE.path)
});
const AwsParameterStoreSyncOptionsSchema = z.object({
keyId: z
.string()
.regex(/^([a-zA-Z0-9:/_-]+)$/, "Invalid KMS Key ID")
.min(1, "Invalid KMS Key ID")
.max(256, "Invalid KMS Key ID")
.refine(
(val) =>
characterValidator([
CharacterType.AlphaNumeric,
CharacterType.Colon,
CharacterType.ForwardSlash,
CharacterType.Underscore,
CharacterType.Hyphen
])(val),
"Invalid KMS Key ID"
)
.optional()
.describe(SecretSyncs.ADDITIONAL_SYNC_OPTIONS.AWS_PARAMETER_STORE.keyId),
tags: z
.object({
key: z
.string()
.regex(
/^([\p{L}\p{Z}\p{N}_.:/=+\-@]*)$/u,
"Invalid resource tag key: keys can only contain Unicode letters, digits, white space and any of the following: _.:/=+@-"
)
.min(1, "Resource tag key required")
.max(128, "Resource tag key cannot exceed 128 characters"),
.max(128, "Resource tag key cannot exceed 128 characters")
.refine(
(val) => tagFieldCharacterValidator(val),
"Invalid resource tag key: keys can only contain Unicode letters, digits, white space and any of the following: _.:/=+@-"
),
value: z
.string()
.regex(
/^([\p{L}\p{Z}\p{N}_.:/=+\-@]*)$/u,
.max(256, "Resource tag value cannot exceed 256 characters")
.refine(
(val) => tagFieldCharacterValidator(val),
"Invalid resource tag value: tag values can only contain Unicode letters, digits, white space and any of the following: _.:/=+@-"
)
.max(256, "Resource tag value cannot exceed 256 characters")
})
.array()
.max(50)
@@ -1,6 +1,7 @@
import { z } from "zod";
import { SecretSyncs } from "@app/lib/api-docs";
import { CharacterType, characterValidator } from "@app/lib/validator/validate-string";
import { AppConnection, AWSRegion } from "@app/services/app-connection/app-connection-enums";
import { AwsSecretsManagerSyncMappingBehavior } from "@app/services/secret-sync/aws-secrets-manager/aws-secrets-manager-sync-enums";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
@@ -24,12 +25,23 @@ const AwsSecretsManagerSyncDestinationConfigSchema = z
.describe(SecretSyncs.DESTINATION_CONFIG.AWS_SECRETS_MANAGER.mappingBehavior),
secretName: z
.string()
.regex(
/^[a-zA-Z0-9/_+=.@-]+$/,
"Secret name must contain only alphanumeric characters and the characters /_+=.@-"
)
.min(1, "Secret name is required")
.max(256, "Secret name cannot exceed 256 characters")
.refine(
(val) =>
characterValidator([
CharacterType.AlphaNumeric,
CharacterType.ForwardSlash,
CharacterType.Underscore,
CharacterType.Plus,
CharacterType.Equals,
CharacterType.Period,
CharacterType.At,
CharacterType.Hyphen
])(val),
"Secret name must contain only alphanumeric characters and the characters /_+=.@-"
)
.describe(SecretSyncs.DESTINATION_CONFIG.AWS_SECRETS_MANAGER.secretName)
})
])
@@ -39,31 +51,54 @@ const AwsSecretsManagerSyncDestinationConfigSchema = z
})
);
const tagFieldCharacterValidator = characterValidator([
CharacterType.AlphaNumeric,
CharacterType.Spaces,
CharacterType.Period,
CharacterType.Underscore,
CharacterType.Colon,
CharacterType.ForwardSlash,
CharacterType.Equals,
CharacterType.Plus,
CharacterType.Hyphen,
CharacterType.At
]);
const AwsSecretsManagerSyncOptionsSchema = z.object({
keyId: z
.string()
.regex(/^([a-zA-Z0-9:/_-]+)$/, "Invalid KMS Key ID")
.min(1, "Invalid KMS Key ID")
.max(256, "Invalid KMS Key ID")
.refine(
(val) =>
characterValidator([
CharacterType.AlphaNumeric,
CharacterType.Colon,
CharacterType.ForwardSlash,
CharacterType.Underscore,
CharacterType.Hyphen
])(val),
"Invalid KMS Key ID"
)
.optional()
.describe(SecretSyncs.ADDITIONAL_SYNC_OPTIONS.AWS_SECRETS_MANAGER.keyId),
tags: z
.object({
key: z
.string()
.regex(
/^([\p{L}\p{Z}\p{N}_.:/=+\-@]*)$/u,
"Invalid tag key: keys can only contain Unicode letters, digits, white space and any of the following: _.:/=+@-"
)
.min(1, "Tag key required")
.max(128, "Tag key cannot exceed 128 characters"),
.max(128, "Tag key cannot exceed 128 characters")
.refine(
(val) => tagFieldCharacterValidator(val),
"Invalid resource tag key: keys can only contain Unicode letters, digits, white space and any of the following: _.:/=+@-"
),
value: z
.string()
.regex(
/^([\p{L}\p{Z}\p{N}_.:/=+\-@]*)$/u,
"Invalid tag value: tag values can only contain Unicode letters, digits, white space and any of the following: _.:/=+@-"
)
.max(256, "Tag value cannot exceed 256 characters")
.refine(
(val) => tagFieldCharacterValidator(val),
"Invalid resource tag value: tag values can only contain Unicode letters, digits, white space and any of the following: _.:/=+@-"
)
})
.array()
.max(50)
@@ -100,7 +100,7 @@ export const azureKeyVaultSyncFactory = ({ kmsService, appConnectionDAL }: TAzur
const deleteSecrets: string[] = [];
Object.keys(secretMap).forEach((infisicalKey) => {
const hyphenatedKey = infisicalKey.replace(/_/g, "-");
const hyphenatedKey = infisicalKey.replaceAll("_", "-");
if (!(hyphenatedKey in vaultSecrets)) {
// case: secret has been created
setSecrets.push({
@@ -117,7 +117,7 @@ export const azureKeyVaultSyncFactory = ({ kmsService, appConnectionDAL }: TAzur
});
Object.keys(vaultSecrets).forEach((key) => {
const underscoredKey = key.replace(/-/g, "_");
const underscoredKey = key.replaceAll("-", "_");
if (!(underscoredKey in secretMap)) {
deleteSecrets.push(key);
}
@@ -211,7 +211,7 @@ export const azureKeyVaultSyncFactory = ({ kmsService, appConnectionDAL }: TAzur
);
for await (const [key] of Object.entries(vaultSecrets)) {
const underscoredKey = key.replace(/-/g, "_");
const underscoredKey = key.replaceAll("-", "_");
if (underscoredKey in secretMap) {
if (!disabledAzureKeyVaultSecretKeys.includes(underscoredKey)) {
@@ -237,7 +237,7 @@ export const azureKeyVaultSyncFactory = ({ kmsService, appConnectionDAL }: TAzur
Object.keys(vaultSecrets).forEach((key) => {
if (!disabledAzureKeyVaultSecretKeys.includes(key)) {
const underscoredKey = key.replace(/-/g, "_");
const underscoredKey = key.replaceAll("-", "_");
secretMap[underscoredKey] = {
value: vaultSecrets[key].value
};
@@ -463,7 +463,7 @@ export const recursivelyGetSecretPaths = async ({
const formatMultiValueEnv = (val?: string) => {
if (!val) return "";
if (!val.match("\n")) return val;
return `"${val.replace(/\n/g, "\\n")}"`;
return `"${val.replaceAll("\n", "\\n")}"`;
};
type TSecretReferenceTraceNode = {
+2 -2
View File
@@ -207,7 +207,7 @@ export const recursivelyGetSecretPaths = ({
const formatMultiValueEnv = (val?: string) => {
if (!val) return "";
if (!val.match("\n")) return val;
return `"${val.replace(/\n/g, "\\n")}"`;
return `"${val.replaceAll("\n", "\\n")}"`;
};
type TInterpolateSecretArg = {
@@ -218,7 +218,7 @@ type TInterpolateSecretArg = {
};
const MAX_SECRET_REFERENCE_DEPTH = 5;
const INTERPOLATION_SYNTAX_REG = /\${([^}]+)}/g;
const INTERPOLATION_SYNTAX_REG = /\${([a-zA-Z0-9-_.]+)}/g;
export const interpolateSecrets = ({ projectId, secretEncKey, secretDAL, folderDAL }: TInterpolateSecretArg) => {
const secretCache: Record<string, Record<string, string>> = {};
const getCacheUniqueKey = (environment: string, secretPath: string) => `${environment}-${secretPath}`;