mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-12 07:29:35 +00:00
383 lines
15 KiB
TypeScript
383 lines
15 KiB
TypeScript
import { ForbiddenError, subject } from "@casl/ability";
|
|
import { packRules } from "@casl/ability/extra";
|
|
|
|
import { ActionProjectType, TableName } from "@app/db/schemas";
|
|
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
|
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
|
import { ms } from "@app/lib/ms";
|
|
import { unpackPermissions } from "@app/server/routes/sanitizedSchema/permission";
|
|
import { ActorType } from "@app/services/auth/auth-type";
|
|
import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
|
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
|
|
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
|
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
|
import { TIdentityProjectAdditionalPrivilegeV2DALFactory } from "./identity-project-additional-privilege-v2-dal";
|
|
import {
|
|
IdentityProjectAdditionalPrivilegeTemporaryMode,
|
|
TCreateIdentityPrivilegeDTO,
|
|
TDeleteIdentityPrivilegeByIdDTO,
|
|
TGetIdentityPrivilegeDetailsByIdDTO,
|
|
TGetIdentityPrivilegeDetailsBySlugDTO,
|
|
TListIdentityPrivilegesDTO,
|
|
TUpdateIdentityPrivilegeByIdDTO
|
|
} from "./identity-project-additional-privilege-v2-types";
|
|
|
|
type TIdentityProjectAdditionalPrivilegeV2ServiceFactoryDep = {
|
|
identityProjectAdditionalPrivilegeDAL: TIdentityProjectAdditionalPrivilegeV2DALFactory;
|
|
identityProjectDAL: Pick<TIdentityProjectDALFactory, "findOne" | "findById">;
|
|
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
|
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
|
};
|
|
|
|
export type TIdentityProjectAdditionalPrivilegeV2ServiceFactory = ReturnType<
|
|
typeof identityProjectAdditionalPrivilegeV2ServiceFactory
|
|
>;
|
|
|
|
export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|
identityProjectAdditionalPrivilegeDAL,
|
|
identityProjectDAL,
|
|
projectDAL,
|
|
permissionService
|
|
}: TIdentityProjectAdditionalPrivilegeV2ServiceFactoryDep) => {
|
|
const create = async ({
|
|
slug,
|
|
actor,
|
|
actorId,
|
|
projectId,
|
|
actorOrgId,
|
|
identityId,
|
|
permissions: customPermission,
|
|
actorAuthMethod,
|
|
...dto
|
|
}: TCreateIdentityPrivilegeDTO) => {
|
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
|
if (!identityProjectMembership)
|
|
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
|
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
actor,
|
|
actorId,
|
|
projectId: identityProjectMembership.projectId,
|
|
actorAuthMethod,
|
|
actorOrgId,
|
|
actionProjectType: ActionProjectType.Any
|
|
});
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
ProjectPermissionActions.Edit,
|
|
subject(ProjectPermissionSub.Identity, { identityId })
|
|
);
|
|
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission({
|
|
actor: ActorType.IDENTITY,
|
|
actorId: identityId,
|
|
projectId: identityProjectMembership.projectId,
|
|
actorAuthMethod,
|
|
actorOrgId,
|
|
actionProjectType: ActionProjectType.Any
|
|
});
|
|
|
|
// we need to validate that the privilege given is not higher than the assigning users permission
|
|
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
|
targetIdentityPermission.update(targetIdentityPermission.rules.concat(customPermission));
|
|
const permissionBoundary = validatePermissionBoundary(permission, targetIdentityPermission);
|
|
if (!permissionBoundary.isValid)
|
|
throw new ForbiddenRequestError({
|
|
name: "PermissionBoundaryError",
|
|
message: "Failed to update more privileged identity",
|
|
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
});
|
|
|
|
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
|
|
slug,
|
|
projectMembershipId: identityProjectMembership.id
|
|
});
|
|
if (existingSlug) throw new BadRequestError({ message: "Additional privilege with provided slug already exists" });
|
|
|
|
const packedPermission = JSON.stringify(packRules(customPermission));
|
|
if (!dto.isTemporary) {
|
|
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({
|
|
projectMembershipId: identityProjectMembership.id,
|
|
slug,
|
|
permissions: packedPermission
|
|
});
|
|
|
|
return {
|
|
...additionalPrivilege,
|
|
permissions: unpackPermissions(additionalPrivilege.permissions)
|
|
};
|
|
}
|
|
|
|
const relativeTempAllocatedTimeInMs = ms(dto.temporaryRange);
|
|
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({
|
|
projectMembershipId: identityProjectMembership.id,
|
|
slug,
|
|
permissions: packedPermission,
|
|
isTemporary: true,
|
|
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative,
|
|
temporaryRange: dto.temporaryRange,
|
|
temporaryAccessStartTime: new Date(dto.temporaryAccessStartTime),
|
|
temporaryAccessEndTime: new Date(new Date(dto.temporaryAccessStartTime).getTime() + relativeTempAllocatedTimeInMs)
|
|
});
|
|
return {
|
|
...additionalPrivilege,
|
|
permissions: unpackPermissions(additionalPrivilege.permissions)
|
|
};
|
|
};
|
|
|
|
const updateById = async ({
|
|
id,
|
|
data,
|
|
actorOrgId,
|
|
actor,
|
|
actorId,
|
|
actorAuthMethod
|
|
}: TUpdateIdentityPrivilegeByIdDTO) => {
|
|
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findById(id);
|
|
if (!identityPrivilege) throw new NotFoundError({ message: `Identity privilege with ${id} not found` });
|
|
|
|
const identityProjectMembership = await identityProjectDAL.findOne({ id: identityPrivilege.projectMembershipId });
|
|
if (!identityProjectMembership)
|
|
throw new NotFoundError({
|
|
message: `Failed to find identity with membership ${identityPrivilege.projectMembershipId}`
|
|
});
|
|
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
actor,
|
|
actorId,
|
|
projectId: identityProjectMembership.projectId,
|
|
actorAuthMethod,
|
|
actorOrgId,
|
|
actionProjectType: ActionProjectType.Any
|
|
});
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
ProjectPermissionActions.Edit,
|
|
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
|
);
|
|
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission({
|
|
actor: ActorType.IDENTITY,
|
|
actorId: identityProjectMembership.identityId,
|
|
projectId: identityProjectMembership.projectId,
|
|
actorAuthMethod,
|
|
actorOrgId,
|
|
actionProjectType: ActionProjectType.Any
|
|
});
|
|
|
|
// we need to validate that the privilege given is not higher than the assigning users permission
|
|
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
|
targetIdentityPermission.update(targetIdentityPermission.rules.concat(data.permissions || []));
|
|
const permissionBoundary = validatePermissionBoundary(permission, targetIdentityPermission);
|
|
if (!permissionBoundary.isValid)
|
|
throw new ForbiddenRequestError({
|
|
name: "PermissionBoundaryError",
|
|
message: "Failed to update more privileged identity",
|
|
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
});
|
|
|
|
if (data?.slug) {
|
|
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
|
|
slug: data.slug,
|
|
projectMembershipId: identityProjectMembership.id
|
|
});
|
|
if (existingSlug && existingSlug.id !== identityPrivilege.id)
|
|
throw new BadRequestError({ message: "Additional privilege with provided slug already exists" });
|
|
}
|
|
|
|
const isTemporary = typeof data?.isTemporary !== "undefined" ? data.isTemporary : identityPrivilege.isTemporary;
|
|
const packedPermission = data.permissions ? JSON.stringify(packRules(data.permissions)) : undefined;
|
|
if (isTemporary) {
|
|
const temporaryAccessStartTime = data?.temporaryAccessStartTime || identityPrivilege?.temporaryAccessStartTime;
|
|
const temporaryRange = data?.temporaryRange || identityPrivilege?.temporaryRange;
|
|
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
|
|
slug: data.slug,
|
|
permissions: packedPermission,
|
|
isTemporary: data.isTemporary,
|
|
temporaryRange: data.temporaryRange,
|
|
temporaryMode: data.temporaryMode,
|
|
temporaryAccessStartTime: new Date(temporaryAccessStartTime || ""),
|
|
temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || ""))
|
|
});
|
|
return {
|
|
...additionalPrivilege,
|
|
permissions: unpackPermissions(additionalPrivilege.permissions)
|
|
};
|
|
}
|
|
|
|
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
|
|
slug: data.slug,
|
|
permissions: packedPermission,
|
|
isTemporary: false,
|
|
temporaryAccessStartTime: null,
|
|
temporaryAccessEndTime: null,
|
|
temporaryRange: null,
|
|
temporaryMode: null
|
|
});
|
|
return {
|
|
...additionalPrivilege,
|
|
permissions: unpackPermissions(additionalPrivilege.permissions)
|
|
};
|
|
};
|
|
|
|
const deleteById = async ({ actorId, id, actor, actorOrgId, actorAuthMethod }: TDeleteIdentityPrivilegeByIdDTO) => {
|
|
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findById(id);
|
|
if (!identityPrivilege) throw new NotFoundError({ message: `Identity privilege with ${id} not found` });
|
|
|
|
const identityProjectMembership = await identityProjectDAL.findOne({ id: identityPrivilege.projectMembershipId });
|
|
if (!identityProjectMembership)
|
|
throw new NotFoundError({
|
|
message: `Failed to find identity with membership ${identityPrivilege.projectMembershipId}`
|
|
});
|
|
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
actor,
|
|
actorId,
|
|
projectId: identityProjectMembership.projectId,
|
|
actorAuthMethod,
|
|
actorOrgId,
|
|
actionProjectType: ActionProjectType.Any
|
|
});
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
ProjectPermissionActions.Edit,
|
|
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
|
);
|
|
const { permission: identityRolePermission } = await permissionService.getProjectPermission({
|
|
actor: ActorType.IDENTITY,
|
|
actorId: identityProjectMembership.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 update more privileged identity",
|
|
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
});
|
|
|
|
const deletedPrivilege = await identityProjectAdditionalPrivilegeDAL.deleteById(identityPrivilege.id);
|
|
return {
|
|
...deletedPrivilege,
|
|
permissions: unpackPermissions(deletedPrivilege.permissions)
|
|
};
|
|
};
|
|
|
|
const getPrivilegeDetailsById = async ({
|
|
id,
|
|
actorOrgId,
|
|
actor,
|
|
actorId,
|
|
actorAuthMethod
|
|
}: TGetIdentityPrivilegeDetailsByIdDTO) => {
|
|
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findById(id);
|
|
if (!identityPrivilege) throw new NotFoundError({ message: `Identity privilege with ${id} not found` });
|
|
|
|
const identityProjectMembership = await identityProjectDAL.findOne({ id: identityPrivilege.projectMembershipId });
|
|
if (!identityProjectMembership)
|
|
throw new NotFoundError({
|
|
message: `Failed to find identity with membership ${identityPrivilege.projectMembershipId}`
|
|
});
|
|
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
actor,
|
|
actorId,
|
|
projectId: identityProjectMembership.projectId,
|
|
actorAuthMethod,
|
|
actorOrgId,
|
|
actionProjectType: ActionProjectType.Any
|
|
});
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
ProjectPermissionActions.Read,
|
|
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
|
);
|
|
|
|
return {
|
|
...identityPrivilege,
|
|
permissions: unpackPermissions(identityPrivilege.permissions)
|
|
};
|
|
};
|
|
|
|
const getPrivilegeDetailsBySlug = async ({
|
|
identityId,
|
|
slug,
|
|
projectSlug,
|
|
actorOrgId,
|
|
actor,
|
|
actorId,
|
|
actorAuthMethod
|
|
}: TGetIdentityPrivilegeDetailsBySlugDTO) => {
|
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
|
if (!project) throw new NotFoundError({ message: `Project with slug ${slug} not found` });
|
|
const projectId = project.id;
|
|
|
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
|
if (!identityProjectMembership)
|
|
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
actor,
|
|
actorId,
|
|
projectId: identityProjectMembership.projectId,
|
|
actorAuthMethod,
|
|
actorOrgId,
|
|
actionProjectType: ActionProjectType.Any
|
|
});
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
ProjectPermissionActions.Read,
|
|
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
|
);
|
|
|
|
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
|
|
slug,
|
|
projectMembershipId: identityProjectMembership.id
|
|
});
|
|
if (!identityPrivilege) throw new NotFoundError({ message: "Identity additional privilege not found" });
|
|
|
|
return {
|
|
...identityPrivilege,
|
|
permissions: unpackPermissions(identityPrivilege.permissions)
|
|
};
|
|
};
|
|
|
|
const listIdentityProjectPrivileges = async ({
|
|
identityId,
|
|
actorOrgId,
|
|
actor,
|
|
actorId,
|
|
actorAuthMethod,
|
|
projectId
|
|
}: TListIdentityPrivilegesDTO) => {
|
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
|
if (!identityProjectMembership)
|
|
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
actor,
|
|
actorId,
|
|
projectId: identityProjectMembership.projectId,
|
|
actorAuthMethod,
|
|
actorOrgId,
|
|
actionProjectType: ActionProjectType.Any
|
|
});
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
ProjectPermissionActions.Read,
|
|
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
|
);
|
|
|
|
const identityPrivileges = await identityProjectAdditionalPrivilegeDAL.find(
|
|
{
|
|
projectMembershipId: identityProjectMembership.id
|
|
},
|
|
{ sort: [[`${TableName.IdentityProjectAdditionalPrivilege}.slug` as "slug", "asc"]] }
|
|
);
|
|
return identityPrivileges;
|
|
};
|
|
|
|
return {
|
|
getPrivilegeDetailsById,
|
|
getPrivilegeDetailsBySlug,
|
|
listIdentityProjectPrivileges,
|
|
create,
|
|
updateById,
|
|
deleteById
|
|
};
|
|
};
|