mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 19:16:22 +00:00
Add project permission cache
This commit is contained in:
@@ -44,7 +44,13 @@ type TGroupServiceFactoryDep = {
|
||||
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findById">;
|
||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete" | "findLatestProjectKey" | "insertMany">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getOrgPermissionByRole">;
|
||||
permissionService: Pick<
|
||||
TPermissionServiceFactory,
|
||||
| "getOrgPermission"
|
||||
| "getOrgPermissionByRole"
|
||||
| "invalidateUserProjectPermissionCache"
|
||||
| "invalidateProjectPermissionCache"
|
||||
>;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
oidcConfigDAL: Pick<TOidcConfigDALFactory, "findOne">;
|
||||
};
|
||||
@@ -225,6 +231,17 @@ export const groupServiceFactory = ({
|
||||
return updated;
|
||||
});
|
||||
|
||||
if (role) {
|
||||
const groupMembers = await userGroupMembershipDAL.find({ groupId: group.id });
|
||||
const groupProjects = await groupProjectDAL.find({ groupId: group.id });
|
||||
await Promise.allSettled([
|
||||
...groupMembers.map((member) => permissionService.invalidateUserProjectPermissionCache(member.userId)),
|
||||
...groupProjects.map((groupProject) =>
|
||||
permissionService.invalidateProjectPermissionCache(groupProject.projectId)
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
return updatedGroup;
|
||||
};
|
||||
|
||||
@@ -247,11 +264,19 @@ export const groupServiceFactory = ({
|
||||
message: "Failed to delete group due to plan restriction. Upgrade plan to delete group."
|
||||
});
|
||||
|
||||
const groupMembers = await userGroupMembershipDAL.find({ groupId: id });
|
||||
const groupProjects = await groupProjectDAL.find({ groupId: id });
|
||||
|
||||
const [group] = await groupDAL.delete({
|
||||
id,
|
||||
orgId: actorOrgId
|
||||
});
|
||||
|
||||
await Promise.allSettled([
|
||||
...groupMembers.map((member) => permissionService.invalidateUserProjectPermissionCache(member.userId)),
|
||||
...groupProjects.map((groupProject) => permissionService.invalidateProjectPermissionCache(groupProject.projectId))
|
||||
]);
|
||||
|
||||
return group;
|
||||
};
|
||||
|
||||
@@ -398,6 +423,12 @@ export const groupServiceFactory = ({
|
||||
projectBotDAL
|
||||
});
|
||||
|
||||
const groupProjects = await groupProjectDAL.find({ groupId: group.id });
|
||||
await Promise.allSettled([
|
||||
permissionService.invalidateUserProjectPermissionCache(user.id),
|
||||
...groupProjects.map((groupProject) => permissionService.invalidateProjectPermissionCache(groupProject.projectId))
|
||||
]);
|
||||
|
||||
return users[0];
|
||||
};
|
||||
|
||||
@@ -479,6 +510,12 @@ export const groupServiceFactory = ({
|
||||
projectKeyDAL
|
||||
});
|
||||
|
||||
const groupProjects = await groupProjectDAL.find({ groupId: group.id });
|
||||
await Promise.allSettled([
|
||||
permissionService.invalidateUserProjectPermissionCache(user.id),
|
||||
...groupProjects.map((groupProject) => permissionService.invalidateProjectPermissionCache(groupProject.projectId))
|
||||
]);
|
||||
|
||||
return users[0];
|
||||
};
|
||||
|
||||
|
||||
+33
-1
@@ -28,7 +28,10 @@ type TIdentityProjectAdditionalPrivilegeV2ServiceFactoryDep = {
|
||||
identityProjectAdditionalPrivilegeDAL: TIdentityProjectAdditionalPrivilegeV2DALFactory;
|
||||
identityProjectDAL: Pick<TIdentityProjectDALFactory, "findOne" | "findById">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
permissionService: Pick<
|
||||
TPermissionServiceFactory,
|
||||
"getProjectPermission" | "invalidateProjectPermissionCache" | "invalidateIdentityProjectPermissionCache"
|
||||
>;
|
||||
};
|
||||
|
||||
export type TIdentityProjectAdditionalPrivilegeV2ServiceFactory = ReturnType<
|
||||
@@ -115,6 +118,11 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
||||
permissions: packedPermission
|
||||
});
|
||||
|
||||
await Promise.allSettled([
|
||||
permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId),
|
||||
permissionService.invalidateIdentityProjectPermissionCache(identityProjectMembership.identityId)
|
||||
]);
|
||||
|
||||
return {
|
||||
...additionalPrivilege,
|
||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||
@@ -132,6 +140,12 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
||||
temporaryAccessStartTime: new Date(dto.temporaryAccessStartTime),
|
||||
temporaryAccessEndTime: new Date(new Date(dto.temporaryAccessStartTime).getTime() + relativeTempAllocatedTimeInMs)
|
||||
});
|
||||
|
||||
await Promise.allSettled([
|
||||
permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId),
|
||||
permissionService.invalidateIdentityProjectPermissionCache(identityProjectMembership.identityId)
|
||||
]);
|
||||
|
||||
return {
|
||||
...additionalPrivilege,
|
||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||
@@ -224,6 +238,12 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
||||
temporaryAccessStartTime: new Date(temporaryAccessStartTime || ""),
|
||||
temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || ""))
|
||||
});
|
||||
|
||||
await Promise.allSettled([
|
||||
permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId),
|
||||
permissionService.invalidateIdentityProjectPermissionCache(identityProjectMembership.identityId)
|
||||
]);
|
||||
|
||||
return {
|
||||
...additionalPrivilege,
|
||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||
@@ -239,6 +259,12 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
||||
temporaryRange: null,
|
||||
temporaryMode: null
|
||||
});
|
||||
|
||||
await Promise.allSettled([
|
||||
permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId),
|
||||
permissionService.invalidateIdentityProjectPermissionCache(identityProjectMembership.identityId)
|
||||
]);
|
||||
|
||||
return {
|
||||
...additionalPrivilege,
|
||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||
@@ -294,6 +320,12 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
||||
});
|
||||
|
||||
const deletedPrivilege = await identityProjectAdditionalPrivilegeDAL.deleteById(identityPrivilege.id);
|
||||
|
||||
await Promise.allSettled([
|
||||
permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId),
|
||||
permissionService.invalidateIdentityProjectPermissionCache(identityProjectMembership.identityId)
|
||||
]);
|
||||
|
||||
return {
|
||||
...deletedPrivilege,
|
||||
permissions: unpackPermissions(deletedPrivilege.permissions)
|
||||
|
||||
+34
-2
@@ -31,7 +31,10 @@ type TIdentityProjectAdditionalPrivilegeServiceFactoryDep = {
|
||||
identityProjectAdditionalPrivilegeDAL: TIdentityProjectAdditionalPrivilegeDALFactory;
|
||||
identityProjectDAL: Pick<TIdentityProjectDALFactory, "findOne" | "findById">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
permissionService: Pick<
|
||||
TPermissionServiceFactory,
|
||||
"getProjectPermission" | "invalidateProjectPermissionCache" | "invalidateIdentityProjectPermissionCache"
|
||||
>;
|
||||
};
|
||||
|
||||
export type TIdentityProjectAdditionalPrivilegeServiceFactory = ReturnType<
|
||||
@@ -129,6 +132,12 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
||||
slug,
|
||||
permissions: packedPermission
|
||||
});
|
||||
|
||||
await Promise.allSettled([
|
||||
permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId),
|
||||
permissionService.invalidateIdentityProjectPermissionCache(identityId)
|
||||
]);
|
||||
|
||||
return {
|
||||
...additionalPrivilege,
|
||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||
@@ -146,6 +155,12 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
||||
temporaryAccessStartTime: new Date(dto.temporaryAccessStartTime),
|
||||
temporaryAccessEndTime: new Date(new Date(dto.temporaryAccessStartTime).getTime() + relativeTempAllocatedTimeInMs)
|
||||
});
|
||||
|
||||
await Promise.allSettled([
|
||||
permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId),
|
||||
permissionService.invalidateIdentityProjectPermissionCache(identityId)
|
||||
]);
|
||||
|
||||
return {
|
||||
...additionalPrivilege,
|
||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||
@@ -250,6 +265,12 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
||||
temporaryAccessStartTime: new Date(temporaryAccessStartTime || ""),
|
||||
temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || ""))
|
||||
});
|
||||
|
||||
await Promise.allSettled([
|
||||
permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId),
|
||||
permissionService.invalidateIdentityProjectPermissionCache(identityId)
|
||||
]);
|
||||
|
||||
return {
|
||||
...additionalPrivilege,
|
||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||
@@ -265,6 +286,12 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
||||
temporaryRange: null,
|
||||
temporaryMode: null
|
||||
});
|
||||
|
||||
await Promise.allSettled([
|
||||
permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId),
|
||||
permissionService.invalidateIdentityProjectPermissionCache(identityId)
|
||||
]);
|
||||
|
||||
return {
|
||||
...additionalPrivilege,
|
||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||
@@ -338,9 +365,14 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
||||
}
|
||||
|
||||
const deletedPrivilege = await identityProjectAdditionalPrivilegeDAL.deleteById(identityPrivilege.id);
|
||||
|
||||
await Promise.allSettled([
|
||||
permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId),
|
||||
permissionService.invalidateIdentityProjectPermissionCache(identityId)
|
||||
]);
|
||||
|
||||
return {
|
||||
...deletedPrivilege,
|
||||
|
||||
permissions: unpackPermissions(deletedPrivilege.permissions)
|
||||
};
|
||||
};
|
||||
|
||||
@@ -283,4 +283,7 @@ export type TPermissionServiceFactory = {
|
||||
projectId: string;
|
||||
checkPermissions: ProjectPermissionSet;
|
||||
}) => Promise<boolean>;
|
||||
invalidateProjectPermissionCache: (projectId: string) => Promise<void>;
|
||||
invalidateUserProjectPermissionCache: (userId: string) => Promise<void>;
|
||||
invalidateIdentityProjectPermissionCache: (identityId: string) => Promise<void>;
|
||||
};
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
projectViewerPermission,
|
||||
sshHostBootstrapPermissions
|
||||
} from "@app/ee/services/permission/default-roles";
|
||||
import { KeyStorePrefixes, KeyStoreTtls, TKeyStoreFactory } from "@app/keystore/keystore";
|
||||
import { conditionsMatcher } from "@app/lib/casl";
|
||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { objectify } from "@app/lib/fn";
|
||||
@@ -49,6 +50,7 @@ type TPermissionServiceFactoryDep = {
|
||||
serviceTokenDAL: Pick<TServiceTokenDALFactory, "findById">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findById">;
|
||||
permissionDAL: TPermissionDALFactory;
|
||||
keyStore: TKeyStoreFactory;
|
||||
};
|
||||
|
||||
export const permissionServiceFactory = ({
|
||||
@@ -56,7 +58,8 @@ export const permissionServiceFactory = ({
|
||||
orgRoleDAL,
|
||||
projectRoleDAL,
|
||||
serviceTokenDAL,
|
||||
projectDAL
|
||||
projectDAL,
|
||||
keyStore
|
||||
}: TPermissionServiceFactoryDep): TPermissionServiceFactory => {
|
||||
const buildOrgPermission = (orgUserRoles: TBuildOrgPermissionDTO) => {
|
||||
const rules = orgUserRoles
|
||||
@@ -83,6 +86,21 @@ export const permissionServiceFactory = ({
|
||||
});
|
||||
};
|
||||
|
||||
const invalidateProjectPermissionCache = async (projectId: string) => {
|
||||
const pattern = KeyStorePrefixes.ProjectPermissionPattern(projectId);
|
||||
await keyStore.deleteItems({ pattern });
|
||||
};
|
||||
|
||||
const invalidateUserProjectPermissionCache = async (userId: string) => {
|
||||
const pattern = KeyStorePrefixes.UserProjectPermissionPattern(userId);
|
||||
await keyStore.deleteItems({ pattern });
|
||||
};
|
||||
|
||||
const invalidateIdentityProjectPermissionCache = async (identityId: string) => {
|
||||
const pattern = KeyStorePrefixes.IdentityProjectPermissionPattern(identityId);
|
||||
await keyStore.deleteItems({ pattern });
|
||||
};
|
||||
|
||||
const buildProjectPermissionRules = (projectUserRoles: TBuildProjectPermissionDTO) => {
|
||||
const rules = projectUserRoles
|
||||
.map(({ role, permissions }) => {
|
||||
@@ -577,35 +595,90 @@ export const permissionServiceFactory = ({
|
||||
actorId = assumedPrivilegeDetailsCtx.actorId;
|
||||
}
|
||||
|
||||
const cacheKey = KeyStorePrefixes.ProjectPermission(
|
||||
projectId,
|
||||
actor,
|
||||
actorId,
|
||||
actionProjectType || ActionProjectType.Any
|
||||
);
|
||||
|
||||
if (actor === ActorType.SERVICE) {
|
||||
return getServiceTokenProjectPermission({
|
||||
serviceTokenId: actorId,
|
||||
projectId,
|
||||
actorOrgId,
|
||||
actionProjectType
|
||||
}) as Promise<TProjectPermissionRT<T>>;
|
||||
}
|
||||
|
||||
try {
|
||||
const cachedData = await keyStore.getItem(cacheKey);
|
||||
if (cachedData) {
|
||||
const parsed = JSON.parse(cachedData) as {
|
||||
rules: RawRuleOf<MongoAbility<ProjectPermissionSet>>[];
|
||||
membership: {
|
||||
roles?: Array<{ role: string; customRoleSlug?: string }>;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
// Reconstruct the permission object from cached rules
|
||||
const permission = createMongoAbility<ProjectPermissionSet>(parsed.rules, {
|
||||
conditionsMatcher
|
||||
});
|
||||
|
||||
return {
|
||||
permission,
|
||||
membership: parsed.membership,
|
||||
hasRole: (role: string) =>
|
||||
parsed.membership.roles?.findIndex(
|
||||
({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug
|
||||
) !== -1
|
||||
} as TProjectPermissionRT<T>;
|
||||
}
|
||||
} catch (error) {}
|
||||
|
||||
let result: TProjectPermissionRT<T>;
|
||||
|
||||
switch (actor) {
|
||||
case ActorType.USER:
|
||||
return getUserProjectPermission({
|
||||
result = (await getUserProjectPermission({
|
||||
userId: actorId,
|
||||
projectId,
|
||||
authMethod: actorAuthMethod,
|
||||
userOrgId: actorOrgId,
|
||||
actionProjectType
|
||||
}) as Promise<TProjectPermissionRT<T>>;
|
||||
case ActorType.SERVICE:
|
||||
return getServiceTokenProjectPermission({
|
||||
serviceTokenId: actorId,
|
||||
projectId,
|
||||
actorOrgId,
|
||||
actionProjectType
|
||||
}) as Promise<TProjectPermissionRT<T>>;
|
||||
})) as TProjectPermissionRT<T>;
|
||||
break;
|
||||
case ActorType.IDENTITY:
|
||||
return getIdentityProjectPermission({
|
||||
result = (await getIdentityProjectPermission({
|
||||
identityId: actorId,
|
||||
projectId,
|
||||
identityOrgId: actorOrgId,
|
||||
actionProjectType
|
||||
}) as Promise<TProjectPermissionRT<T>>;
|
||||
})) as TProjectPermissionRT<T>;
|
||||
break;
|
||||
default:
|
||||
throw new BadRequestError({
|
||||
message: "Invalid actor provided",
|
||||
name: "Get project permission"
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const cacheData = {
|
||||
rules: result.permission.rules,
|
||||
membership: result.membership
|
||||
};
|
||||
|
||||
await keyStore.setItemWithExpiry(
|
||||
cacheKey,
|
||||
KeyStoreTtls.ProjectPermissionCacheInSeconds,
|
||||
JSON.stringify(cacheData)
|
||||
);
|
||||
} catch (error) {
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const getProjectPermissionByRole: TPermissionServiceFactory["getProjectPermissionByRole"] = async (
|
||||
@@ -668,6 +741,9 @@ export const permissionServiceFactory = ({
|
||||
getProjectPermissionByRole,
|
||||
buildOrgPermission,
|
||||
buildProjectPermissionRules,
|
||||
checkGroupProjectPermission
|
||||
checkGroupProjectPermission,
|
||||
invalidateProjectPermissionCache,
|
||||
invalidateUserProjectPermissionCache,
|
||||
invalidateIdentityProjectPermissionCache
|
||||
};
|
||||
};
|
||||
|
||||
+33
-1
@@ -27,7 +27,10 @@ import {
|
||||
type TProjectUserAdditionalPrivilegeServiceFactoryDep = {
|
||||
projectUserAdditionalPrivilegeDAL: TProjectUserAdditionalPrivilegeDALFactory;
|
||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findById" | "findOne">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
permissionService: Pick<
|
||||
TPermissionServiceFactory,
|
||||
"getProjectPermission" | "invalidateUserProjectPermissionCache" | "invalidateProjectPermissionCache"
|
||||
>;
|
||||
accessApprovalRequestDAL: Pick<TAccessApprovalRequestDALFactory, "update">;
|
||||
};
|
||||
|
||||
@@ -115,6 +118,12 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
slug,
|
||||
permissions: packedPermission
|
||||
});
|
||||
|
||||
await Promise.allSettled([
|
||||
permissionService.invalidateProjectPermissionCache(projectMembership.projectId),
|
||||
permissionService.invalidateUserProjectPermissionCache(projectMembership.userId)
|
||||
]);
|
||||
|
||||
return {
|
||||
...additionalPrivilege,
|
||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||
@@ -133,6 +142,12 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
temporaryAccessStartTime: new Date(dto.temporaryAccessStartTime),
|
||||
temporaryAccessEndTime: new Date(new Date(dto.temporaryAccessStartTime).getTime() + relativeTempAllocatedTimeInMs)
|
||||
});
|
||||
|
||||
await Promise.allSettled([
|
||||
permissionService.invalidateProjectPermissionCache(projectMembership.projectId),
|
||||
permissionService.invalidateUserProjectPermissionCache(projectMembership.userId)
|
||||
]);
|
||||
|
||||
return {
|
||||
...additionalPrivilege,
|
||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||
@@ -230,6 +245,11 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || ""))
|
||||
});
|
||||
|
||||
await Promise.allSettled([
|
||||
permissionService.invalidateProjectPermissionCache(projectMembership.projectId),
|
||||
permissionService.invalidateUserProjectPermissionCache(projectMembership.userId)
|
||||
]);
|
||||
|
||||
return {
|
||||
...additionalPrivilege,
|
||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||
@@ -245,6 +265,12 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
temporaryRange: null,
|
||||
temporaryMode: null
|
||||
});
|
||||
|
||||
await Promise.allSettled([
|
||||
permissionService.invalidateProjectPermissionCache(projectMembership.projectId),
|
||||
permissionService.invalidateUserProjectPermissionCache(projectMembership.userId)
|
||||
]);
|
||||
|
||||
return {
|
||||
...additionalPrivilege,
|
||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||
@@ -291,6 +317,12 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
}
|
||||
);
|
||||
const deletedPrivilege = await projectUserAdditionalPrivilegeDAL.deleteById(userPrivilege.id);
|
||||
|
||||
await Promise.allSettled([
|
||||
permissionService.invalidateProjectPermissionCache(projectMembership.projectId),
|
||||
permissionService.invalidateUserProjectPermissionCache(projectMembership.userId)
|
||||
]);
|
||||
|
||||
return {
|
||||
...deletedPrivilege,
|
||||
permissions: unpackPermissions(deletedPrivilege.permissions)
|
||||
|
||||
Reference in New Issue
Block a user