mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 12:38:36 +00:00
feat: fixing ts
This commit is contained in:
@@ -78,10 +78,7 @@ type TGithubOrgSyncServiceFactoryDep = {
|
||||
>;
|
||||
groupDAL: Pick<TGroupDALFactory, "insertMany" | "transaction" | "find">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
orgMembershipDAL: Pick<
|
||||
TOrgMembershipDALFactory,
|
||||
"find" | "findOrgMembershipById" | "findOrgMembershipsWithUsersByOrgId"
|
||||
>;
|
||||
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "findOrgMembershipById" | "findOrgMembershipsWithUsersByOrgId">;
|
||||
};
|
||||
|
||||
export type TGithubOrgSyncServiceFactory = ReturnType<typeof githubOrgSyncServiceFactory>;
|
||||
|
||||
@@ -195,12 +195,36 @@ export const groupDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const findOne = async (filter: Partial<TGroups>, tx?: Knex): Promise<TGroups | undefined> => {
|
||||
try {
|
||||
const doc = await (tx || db.replicaNode())(TableName.Groups)
|
||||
.join(TableName.Membership, `${TableName.Membership}.actorGroupId`, `${TableName.Groups}.id`)
|
||||
.join(TableName.MembershipRole, `${TableName.MembershipRole}.membershipId`, `${TableName.Membership}.id`)
|
||||
.where(`${TableName.Membership}.scope`, AccessScope.Organization)
|
||||
.where((queryBuilder) => {
|
||||
Object.entries(filter).forEach(([key, value]) => {
|
||||
void queryBuilder.where(`${TableName.Membership}.${key}`, value);
|
||||
});
|
||||
})
|
||||
.select(
|
||||
selectAllTableCols(TableName.Groups),
|
||||
db.ref("role").withSchema(TableName.MembershipRole),
|
||||
db.ref("customRoleId").as("roleId").withSchema(TableName.MembershipRole)
|
||||
)
|
||||
.first();
|
||||
return doc;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find one" });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...groupOrm,
|
||||
findGroups,
|
||||
findByOrgId,
|
||||
findAllGroupPossibleMembers,
|
||||
findGroupsByProjectId,
|
||||
findById
|
||||
findById,
|
||||
findOne
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { ProjectVersion, SecretKeyEncoding, TableName, TUsers } from "@app/db/schemas";
|
||||
import { AccessScope, ProjectVersion, SecretKeyEncoding, TableName, TUsers } from "@app/db/schemas";
|
||||
import { crypto } from "@app/lib/crypto/cryptography";
|
||||
import { BadRequestError, ForbiddenRequestError, NotFoundError, ScimRequestError } from "@app/lib/errors";
|
||||
|
||||
@@ -16,7 +16,7 @@ const addAcceptedUsersToGroup = async ({
|
||||
group,
|
||||
userGroupMembershipDAL,
|
||||
userDAL,
|
||||
groupProjectDAL,
|
||||
membershipGroupDAL,
|
||||
projectKeyDAL,
|
||||
projectDAL,
|
||||
projectBotDAL,
|
||||
@@ -42,13 +42,15 @@ const addAcceptedUsersToGroup = async ({
|
||||
const projectIds = Array.from(
|
||||
new Set(
|
||||
(
|
||||
await groupProjectDAL.find(
|
||||
await membershipGroupDAL.find(
|
||||
{
|
||||
groupId: group.id
|
||||
actorGroupId: group.id,
|
||||
scopeOrgId: group.orgId,
|
||||
scope: AccessScope.Project
|
||||
},
|
||||
{ tx }
|
||||
)
|
||||
).map((gp) => gp.projectId)
|
||||
).map((gp) => gp.scopeProjectId as string)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -167,11 +169,11 @@ export const addUsersToGroupByUserIds = async ({
|
||||
userDAL,
|
||||
userGroupMembershipDAL,
|
||||
orgDAL,
|
||||
groupProjectDAL,
|
||||
projectKeyDAL,
|
||||
projectDAL,
|
||||
projectBotDAL,
|
||||
tx: outerTx
|
||||
tx: outerTx,
|
||||
membershipGroupDAL
|
||||
}: TAddUsersToGroupByUserIds) => {
|
||||
const processAddition = async (tx: Knex) => {
|
||||
const foundMembers = await userDAL.find(
|
||||
@@ -214,15 +216,18 @@ export const addUsersToGroupByUserIds = async ({
|
||||
// check if all user(s) are part of the organization
|
||||
const existingUserOrgMemberships = await orgDAL.findMembership(
|
||||
{
|
||||
[`${TableName.OrgMembership}.orgId` as "orgId"]: group.orgId,
|
||||
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: group.orgId,
|
||||
scope: AccessScope.Organization,
|
||||
$in: {
|
||||
[`${TableName.OrgMembership}.userId` as "userId"]: userIds
|
||||
[`${TableName.Membership}.actorUserId` as "actorUserId"]: userIds
|
||||
}
|
||||
},
|
||||
{ tx }
|
||||
);
|
||||
|
||||
const existingUserOrgMembershipsUserIdsSet = new Set(existingUserOrgMemberships.map((u) => u.userId));
|
||||
const existingUserOrgMembershipsUserIdsSet = new Set(
|
||||
existingUserOrgMemberships.map((u) => u.actorUserId as string)
|
||||
);
|
||||
|
||||
userIds.forEach((userId) => {
|
||||
if (!existingUserOrgMembershipsUserIdsSet.has(userId))
|
||||
@@ -250,7 +255,7 @@ export const addUsersToGroupByUserIds = async ({
|
||||
group,
|
||||
userDAL,
|
||||
userGroupMembershipDAL,
|
||||
groupProjectDAL,
|
||||
membershipGroupDAL,
|
||||
projectKeyDAL,
|
||||
projectDAL,
|
||||
projectBotDAL,
|
||||
@@ -292,9 +297,9 @@ export const removeUsersFromGroupByUserIds = async ({
|
||||
userIds,
|
||||
userDAL,
|
||||
userGroupMembershipDAL,
|
||||
groupProjectDAL,
|
||||
projectKeyDAL,
|
||||
tx: outerTx
|
||||
tx: outerTx,
|
||||
membershipGroupDAL
|
||||
}: TRemoveUsersFromGroupByUserIds) => {
|
||||
const processRemoval = async (tx: Knex) => {
|
||||
const foundMembers = await userDAL.find({
|
||||
@@ -352,13 +357,15 @@ export const removeUsersFromGroupByUserIds = async ({
|
||||
const projectIds = Array.from(
|
||||
new Set(
|
||||
(
|
||||
await groupProjectDAL.find(
|
||||
await membershipGroupDAL.find(
|
||||
{
|
||||
groupId: group.id
|
||||
scope: AccessScope.Project,
|
||||
actorGroupId: group.id,
|
||||
scopeOrgId: group.orgId
|
||||
},
|
||||
{ tx }
|
||||
)
|
||||
).map((gp) => gp.projectId)
|
||||
).map((gp) => gp.scopeProjectId as string)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -422,11 +429,11 @@ export const convertPendingGroupAdditionsToGroupMemberships = async ({
|
||||
userIds,
|
||||
userDAL,
|
||||
userGroupMembershipDAL,
|
||||
groupProjectDAL,
|
||||
projectKeyDAL,
|
||||
projectDAL,
|
||||
projectBotDAL,
|
||||
tx: outerTx
|
||||
tx: outerTx,
|
||||
membershipGroupDAL
|
||||
}: TConvertPendingGroupAdditionsToGroupMemberships) => {
|
||||
const processConversion = async (tx: Knex) => {
|
||||
const users = await userDAL.find(
|
||||
@@ -463,7 +470,7 @@ export const convertPendingGroupAdditionsToGroupMemberships = async ({
|
||||
group: pendingGroupAddition.group,
|
||||
userDAL,
|
||||
userGroupMembershipDAL,
|
||||
groupProjectDAL,
|
||||
membershipGroupDAL,
|
||||
projectKeyDAL,
|
||||
projectDAL,
|
||||
projectBotDAL,
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
|
||||
import { OrgMembershipRole, TOrgRoles } from "@app/db/schemas";
|
||||
import { AccessScope, OrgMembershipRole, TRoles } from "@app/db/schemas";
|
||||
import { TOidcConfigDALFactory } from "@app/ee/services/oidc/oidc-config-dal";
|
||||
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
||||
import { TMembershipRoleDALFactory } from "@app/services/membership/membership-role-dal";
|
||||
import { TMembershipGroupDALFactory } from "@app/services/membership-group/membership-group-dal";
|
||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
||||
@@ -35,8 +36,9 @@ type TGroupServiceFactoryDep = {
|
||||
TGroupDALFactory,
|
||||
"create" | "findOne" | "update" | "delete" | "findAllGroupPossibleMembers" | "findById" | "transaction"
|
||||
>;
|
||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
||||
orgDAL: Pick<TOrgDALFactory, "findMembership" | "countAllOrgMembers">;
|
||||
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "find" | "findOne" | "create">;
|
||||
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "create" | "delete">;
|
||||
orgDAL: Pick<TOrgDALFactory, "findMembership" | "countAllOrgMembers" | "findById">;
|
||||
userGroupMembershipDAL: Pick<
|
||||
TUserGroupMembershipDALFactory,
|
||||
"findOne" | "delete" | "filterProjectsByUserMembership" | "transaction" | "insertMany" | "find"
|
||||
@@ -54,7 +56,6 @@ export type TGroupServiceFactory = ReturnType<typeof groupServiceFactory>;
|
||||
export const groupServiceFactory = ({
|
||||
userDAL,
|
||||
groupDAL,
|
||||
groupProjectDAL,
|
||||
orgDAL,
|
||||
userGroupMembershipDAL,
|
||||
projectDAL,
|
||||
@@ -62,12 +63,14 @@ export const groupServiceFactory = ({
|
||||
projectKeyDAL,
|
||||
permissionService,
|
||||
licenseService,
|
||||
oidcConfigDAL
|
||||
oidcConfigDAL,
|
||||
membershipGroupDAL,
|
||||
membershipRoleDAL
|
||||
}: TGroupServiceFactoryDep) => {
|
||||
const createGroup = async ({ name, slug, role, actor, actorId, actorAuthMethod, actorOrgId }: TCreateGroupDTO) => {
|
||||
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
|
||||
|
||||
const { permission, membership } = await permissionService.getOrgPermission(
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
@@ -82,25 +85,23 @@ export const groupServiceFactory = ({
|
||||
message: "Failed to create group due to plan restriction. Upgrade plan to create group."
|
||||
});
|
||||
|
||||
const { permission: rolePermission, role: customRole } = await permissionService.getOrgPermissionByRole(
|
||||
role,
|
||||
actorOrgId
|
||||
);
|
||||
const isCustomRole = Boolean(customRole);
|
||||
const [rolePermissionDetails] = await permissionService.getOrgPermissionByRoles([role], actorOrgId);
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(actorOrgId);
|
||||
const isCustomRole = Boolean(rolePermissionDetails?.role);
|
||||
if (role !== OrgMembershipRole.NoAccess) {
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionGroupActions.GrantPrivileges,
|
||||
OrgPermissionSubjects.Groups,
|
||||
permission,
|
||||
rolePermission
|
||||
rolePermissionDetails.permission
|
||||
);
|
||||
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to create group",
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionGroupActions.GrantPrivileges,
|
||||
OrgPermissionSubjects.Groups
|
||||
),
|
||||
@@ -122,7 +123,25 @@ export const groupServiceFactory = ({
|
||||
slug: slug || slugify(`${name}-${alphaNumericNanoId(4)}`),
|
||||
orgId: actorOrgId,
|
||||
role: isCustomRole ? OrgMembershipRole.Custom : role,
|
||||
roleId: customRole?.id
|
||||
roleId: rolePermissionDetails?.role?.id
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
const membership = await membershipGroupDAL.create(
|
||||
{
|
||||
actorGroupId: newGroup.id,
|
||||
scope: AccessScope.Organization,
|
||||
scopeOrgId: actorOrgId
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
await membershipRoleDAL.create(
|
||||
{
|
||||
membershipId: membership.id,
|
||||
role: isCustomRole ? OrgMembershipRole.Custom : role,
|
||||
customRoleId: rolePermissionDetails?.role?.id
|
||||
},
|
||||
tx
|
||||
);
|
||||
@@ -145,7 +164,7 @@ export const groupServiceFactory = ({
|
||||
}: TUpdateGroupDTO) => {
|
||||
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
|
||||
|
||||
const { permission, membership } = await permissionService.getOrgPermission(
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
@@ -166,32 +185,31 @@ export const groupServiceFactory = ({
|
||||
throw new NotFoundError({ message: `Failed to find group with ID ${id}` });
|
||||
}
|
||||
|
||||
let customRole: TOrgRoles | undefined;
|
||||
let customRole: TRoles | undefined;
|
||||
if (role) {
|
||||
const { permission: rolePermission, role: customOrgRole } = await permissionService.getOrgPermissionByRole(
|
||||
role,
|
||||
group.orgId
|
||||
);
|
||||
const [rolePermissionDetails] = await permissionService.getOrgPermissionByRoles([role], group.orgId);
|
||||
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(actorOrgId);
|
||||
const isCustomRole = Boolean(rolePermissionDetails?.role);
|
||||
|
||||
const isCustomRole = Boolean(customOrgRole);
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionGroupActions.GrantPrivileges,
|
||||
OrgPermissionSubjects.Groups,
|
||||
permission,
|
||||
rolePermission
|
||||
rolePermissionDetails.permission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to update group",
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionGroupActions.GrantPrivileges,
|
||||
OrgPermissionSubjects.Groups
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
if (isCustomRole) customRole = customOrgRole;
|
||||
if (isCustomRole) customRole = rolePermissionDetails?.role;
|
||||
}
|
||||
|
||||
const updatedGroup = await groupDAL.transaction(async (tx) => {
|
||||
@@ -211,17 +229,31 @@ export const groupServiceFactory = ({
|
||||
},
|
||||
{
|
||||
name,
|
||||
slug: slug ? slugify(slug) : undefined,
|
||||
...(role
|
||||
? {
|
||||
role: customRole ? OrgMembershipRole.Custom : role,
|
||||
roleId: customRole?.id ?? null
|
||||
}
|
||||
: {})
|
||||
slug: slug ? slugify(slug) : undefined
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
if (role) {
|
||||
const membership = await membershipGroupDAL.findOne(
|
||||
{
|
||||
scope: AccessScope.Organization,
|
||||
actorGroupId: updated.id,
|
||||
scopeOrgId: updated.orgId
|
||||
},
|
||||
tx
|
||||
);
|
||||
await membershipRoleDAL.delete({ membershipId: membership.id }, tx);
|
||||
await membershipRoleDAL.create(
|
||||
{
|
||||
membershipId: membership.id,
|
||||
role: customRole ? OrgMembershipRole.Custom : role,
|
||||
customRoleId: customRole?.id ?? null
|
||||
},
|
||||
tx
|
||||
);
|
||||
}
|
||||
|
||||
return updated;
|
||||
});
|
||||
|
||||
@@ -341,7 +373,7 @@ export const groupServiceFactory = ({
|
||||
const addUserToGroup = async ({ id, username, actor, actorId, actorAuthMethod, actorOrgId }: TAddUserToGroupDTO) => {
|
||||
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
|
||||
|
||||
const { permission, membership } = await permissionService.getOrgPermission(
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
@@ -373,22 +405,23 @@ export const groupServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
const { permission: groupRolePermission } = await permissionService.getOrgPermissionByRole(group.role, actorOrgId);
|
||||
const [rolePermissionDetails] = await permissionService.getOrgPermissionByRoles([group.role], actorOrgId);
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(actorOrgId);
|
||||
|
||||
// check if user has broader or equal to privileges than group
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionGroupActions.AddMembers,
|
||||
OrgPermissionSubjects.Groups,
|
||||
permission,
|
||||
groupRolePermission
|
||||
rolePermissionDetails.permission
|
||||
);
|
||||
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to add user to more privileged group",
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionGroupActions.AddMembers,
|
||||
OrgPermissionSubjects.Groups
|
||||
),
|
||||
@@ -407,7 +440,7 @@ export const groupServiceFactory = ({
|
||||
userDAL,
|
||||
userGroupMembershipDAL,
|
||||
orgDAL,
|
||||
groupProjectDAL,
|
||||
membershipGroupDAL,
|
||||
projectKeyDAL,
|
||||
projectDAL,
|
||||
projectBotDAL
|
||||
@@ -431,7 +464,7 @@ export const groupServiceFactory = ({
|
||||
}: TRemoveUserFromGroupDTO) => {
|
||||
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
|
||||
|
||||
const { permission, membership } = await permissionService.getOrgPermission(
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
@@ -463,21 +496,22 @@ export const groupServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
const { permission: groupRolePermission } = await permissionService.getOrgPermissionByRole(group.role, actorOrgId);
|
||||
const [rolePermissionDetails] = await permissionService.getOrgPermissionByRoles([group.role], actorOrgId);
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(actorOrgId);
|
||||
|
||||
// check if user has broader or equal to privileges than group
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionGroupActions.RemoveMembers,
|
||||
OrgPermissionSubjects.Groups,
|
||||
permission,
|
||||
groupRolePermission
|
||||
rolePermissionDetails.permission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to delete user from more privileged group",
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionGroupActions.RemoveMembers,
|
||||
OrgPermissionSubjects.Groups
|
||||
),
|
||||
@@ -495,7 +529,7 @@ export const groupServiceFactory = ({
|
||||
userIds: [user.id],
|
||||
userDAL,
|
||||
userGroupMembershipDAL,
|
||||
groupProjectDAL,
|
||||
membershipGroupDAL,
|
||||
projectKeyDAL
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Knex } from "knex";
|
||||
import { TGroups } from "@app/db/schemas";
|
||||
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
||||
import { TGenericPermission } from "@app/lib/types";
|
||||
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
||||
import { TMembershipGroupDALFactory } from "@app/services/membership-group/membership-group-dal";
|
||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
||||
@@ -63,7 +63,7 @@ export type TAddUsersToGroup = {
|
||||
group: TGroups;
|
||||
userDAL: Pick<TUserDALFactory, "findUserEncKeyByUserIdsBatch">;
|
||||
userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "find" | "transaction" | "insertMany">;
|
||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
||||
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "find">;
|
||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findById">;
|
||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||
@@ -76,7 +76,7 @@ export type TAddUsersToGroupByUserIds = {
|
||||
userDAL: Pick<TUserDALFactory, "find" | "findUserEncKeyByUserIdsBatch" | "transaction">;
|
||||
userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "find" | "transaction" | "insertMany">;
|
||||
orgDAL: Pick<TOrgDALFactory, "findMembership">;
|
||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
||||
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "find">;
|
||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findById">;
|
||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||
@@ -88,7 +88,7 @@ export type TRemoveUsersFromGroupByUserIds = {
|
||||
userIds: string[];
|
||||
userDAL: Pick<TUserDALFactory, "find" | "transaction">;
|
||||
userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "find" | "filterProjectsByUserMembership" | "delete">;
|
||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
||||
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "find">;
|
||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "delete">;
|
||||
tx?: Knex;
|
||||
};
|
||||
@@ -100,7 +100,7 @@ export type TConvertPendingGroupAdditionsToGroupMemberships = {
|
||||
TUserGroupMembershipDALFactory,
|
||||
"find" | "transaction" | "insertMany" | "deletePendingUserGroupMembershipsByUserIds"
|
||||
>;
|
||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
||||
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "find">;
|
||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findById">;
|
||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
|
||||
export type TIdentityProjectAdditionalPrivilegeV2DALFactory = ReturnType<
|
||||
typeof identityProjectAdditionalPrivilegeV2DALFactory
|
||||
>;
|
||||
|
||||
export const identityProjectAdditionalPrivilegeV2DALFactory = (db: TDbClient) => {
|
||||
const orm = ormify(db, TableName.IdentityProjectAdditionalPrivilege);
|
||||
return orm;
|
||||
};
|
||||
-434
@@ -1,434 +0,0 @@
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
import { packRules } from "@casl/ability/extra";
|
||||
|
||||
import { ActionProjectType, TableName } from "@app/db/schemas";
|
||||
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
|
||||
import { ms } from "@app/lib/ms";
|
||||
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
|
||||
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 { constructPermissionErrorMessage, validatePrivilegeChangeOperation } from "../permission/permission-fns";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
||||
import { ProjectPermissionIdentityActions, 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" | "invalidateProjectPermissionCache">;
|
||||
};
|
||||
|
||||
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(
|
||||
ProjectPermissionIdentityActions.Edit,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
const { permission: targetIdentityPermission, membership } = 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 = validatePrivilegeChangeOperation(
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
ProjectPermissionIdentityActions.GrantPrivileges,
|
||||
ProjectPermissionSub.Identity,
|
||||
permission,
|
||||
targetIdentityPermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to update more privileged identity",
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
ProjectPermissionIdentityActions.GrantPrivileges,
|
||||
ProjectPermissionSub.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
validateHandlebarTemplate("Identity Additional Privilege Create", JSON.stringify(customPermission || []), {
|
||||
allowedExpressions: (val) => val.includes("identity.")
|
||||
});
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
|
||||
|
||||
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)
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
|
||||
|
||||
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(
|
||||
ProjectPermissionIdentityActions.Edit,
|
||||
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
||||
);
|
||||
const { permission: targetIdentityPermission, membership } = 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 = validatePrivilegeChangeOperation(
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
ProjectPermissionIdentityActions.GrantPrivileges,
|
||||
ProjectPermissionSub.Identity,
|
||||
permission,
|
||||
targetIdentityPermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to update more privileged identity",
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
ProjectPermissionIdentityActions.GrantPrivileges,
|
||||
ProjectPermissionSub.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
|
||||
validateHandlebarTemplate("Identity Additional Privilege Update", JSON.stringify(data.permissions || []), {
|
||||
allowedExpressions: (val) => val.includes("identity.")
|
||||
});
|
||||
|
||||
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 || ""))
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
|
||||
|
||||
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, membership } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.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 = validatePrivilegeChangeOperation(
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
ProjectPermissionIdentityActions.GrantPrivileges,
|
||||
ProjectPermissionSub.Identity,
|
||||
permission,
|
||||
identityRolePermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to update more privileged identity",
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
ProjectPermissionIdentityActions.GrantPrivileges,
|
||||
ProjectPermissionSub.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
|
||||
const deletedPrivilege = await identityProjectAdditionalPrivilegeDAL.deleteById(identityPrivilege.id);
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
|
||||
|
||||
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(
|
||||
ProjectPermissionIdentityActions.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(
|
||||
ProjectPermissionIdentityActions.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(
|
||||
ProjectPermissionIdentityActions.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
|
||||
};
|
||||
};
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
import { TProjectPermission } from "@app/lib/types";
|
||||
|
||||
import { TProjectPermissionV2Schema } from "../permission/project-permission";
|
||||
|
||||
export enum IdentityProjectAdditionalPrivilegeTemporaryMode {
|
||||
Relative = "relative"
|
||||
}
|
||||
|
||||
export type TCreateIdentityPrivilegeDTO = {
|
||||
permissions: TProjectPermissionV2Schema[];
|
||||
identityId: string;
|
||||
projectId: string;
|
||||
slug: string;
|
||||
} & (
|
||||
| {
|
||||
isTemporary: false;
|
||||
}
|
||||
| {
|
||||
isTemporary: true;
|
||||
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative;
|
||||
temporaryRange: string;
|
||||
temporaryAccessStartTime: string;
|
||||
}
|
||||
) &
|
||||
Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TUpdateIdentityPrivilegeByIdDTO = { id: string } & Omit<TProjectPermission, "projectId"> & {
|
||||
data: Partial<{
|
||||
permissions: TProjectPermissionV2Schema[];
|
||||
slug: string;
|
||||
isTemporary: boolean;
|
||||
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative;
|
||||
temporaryRange: string;
|
||||
temporaryAccessStartTime: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
export type TDeleteIdentityPrivilegeByIdDTO = Omit<TProjectPermission, "projectId"> & {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type TGetIdentityPrivilegeDetailsByIdDTO = Omit<TProjectPermission, "projectId"> & {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type TListIdentityPrivilegesDTO = Omit<TProjectPermission, "projectId"> & {
|
||||
identityId: string;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export type TGetIdentityPrivilegeDetailsBySlugDTO = Omit<TProjectPermission, "projectId"> & {
|
||||
slug: string;
|
||||
identityId: string;
|
||||
projectSlug: string;
|
||||
};
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
|
||||
export type TIdentityProjectAdditionalPrivilegeDALFactory = ReturnType<
|
||||
typeof identityProjectAdditionalPrivilegeDALFactory
|
||||
>;
|
||||
|
||||
export const identityProjectAdditionalPrivilegeDALFactory = (db: TDbClient) => {
|
||||
const orm = ormify(db, TableName.IdentityProjectAdditionalPrivilege);
|
||||
return orm;
|
||||
};
|
||||
-451
@@ -1,451 +0,0 @@
|
||||
import { ForbiddenError, MongoAbility, RawRuleOf, subject } from "@casl/ability";
|
||||
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
|
||||
import { ms } from "@app/lib/ms";
|
||||
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
|
||||
import { UnpackedPermissionSchema } 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 { constructPermissionErrorMessage, validatePrivilegeChangeOperation } from "../permission/permission-fns";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
||||
import {
|
||||
ProjectPermissionIdentityActions,
|
||||
ProjectPermissionSet,
|
||||
ProjectPermissionSub
|
||||
} from "../permission/project-permission";
|
||||
import { TIdentityProjectAdditionalPrivilegeDALFactory } from "./identity-project-additional-privilege-dal";
|
||||
import {
|
||||
IdentityProjectAdditionalPrivilegeTemporaryMode,
|
||||
TCreateIdentityPrivilegeDTO,
|
||||
TDeleteIdentityPrivilegeDTO,
|
||||
TGetIdentityPrivilegeDetailsDTO,
|
||||
TListIdentityPrivilegesDTO,
|
||||
TUpdateIdentityPrivilegeDTO
|
||||
} from "./identity-project-additional-privilege-types";
|
||||
|
||||
type TIdentityProjectAdditionalPrivilegeServiceFactoryDep = {
|
||||
identityProjectAdditionalPrivilegeDAL: TIdentityProjectAdditionalPrivilegeDALFactory;
|
||||
identityProjectDAL: Pick<TIdentityProjectDALFactory, "findOne" | "findById">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "invalidateProjectPermissionCache">;
|
||||
};
|
||||
|
||||
export type TIdentityProjectAdditionalPrivilegeServiceFactory = ReturnType<
|
||||
typeof identityProjectAdditionalPrivilegeServiceFactory
|
||||
>;
|
||||
|
||||
const unpackPermissions = (permissions: unknown) =>
|
||||
UnpackedPermissionSchema.array().parse(
|
||||
unpackRules((permissions || []) as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[])
|
||||
);
|
||||
|
||||
export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
||||
identityProjectAdditionalPrivilegeDAL,
|
||||
identityProjectDAL,
|
||||
permissionService,
|
||||
projectDAL
|
||||
}: TIdentityProjectAdditionalPrivilegeServiceFactoryDep) => {
|
||||
const create = async ({
|
||||
slug,
|
||||
actor,
|
||||
actorId,
|
||||
identityId,
|
||||
projectSlug,
|
||||
permissions: customPermission,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
...dto
|
||||
}: TCreateIdentityPrivilegeDTO) => {
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' 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, membership } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.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 = validatePrivilegeChangeOperation(
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
ProjectPermissionIdentityActions.GrantPrivileges,
|
||||
ProjectPermissionSub.Identity,
|
||||
permission,
|
||||
targetIdentityPermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to update more privileged identity",
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
ProjectPermissionIdentityActions.GrantPrivileges,
|
||||
ProjectPermissionSub.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
|
||||
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||
slug,
|
||||
projectMembershipId: identityProjectMembership.id
|
||||
});
|
||||
if (existingSlug) throw new BadRequestError({ message: "Additional privilege of provided slug exist" });
|
||||
|
||||
validateHandlebarTemplate("Identity Additional Privilege Create", JSON.stringify(customPermission || []), {
|
||||
allowedExpressions: (val) => val.includes("identity.")
|
||||
});
|
||||
|
||||
const packedPermission = JSON.stringify(packRules(customPermission));
|
||||
if (!dto.isTemporary) {
|
||||
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({
|
||||
projectMembershipId: identityProjectMembership.id,
|
||||
slug,
|
||||
permissions: packedPermission
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
|
||||
|
||||
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)
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
|
||||
|
||||
return {
|
||||
...additionalPrivilege,
|
||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||
};
|
||||
};
|
||||
|
||||
const updateBySlug = async ({
|
||||
projectSlug,
|
||||
slug,
|
||||
identityId,
|
||||
data,
|
||||
actorOrgId,
|
||||
actor,
|
||||
actorId,
|
||||
actorAuthMethod
|
||||
}: TUpdateIdentityPrivilegeDTO) => {
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' 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, membership } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Edit,
|
||||
subject(ProjectPermissionSub.Identity, { 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 = validatePrivilegeChangeOperation(
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
ProjectPermissionIdentityActions.GrantPrivileges,
|
||||
ProjectPermissionSub.Identity,
|
||||
permission,
|
||||
targetIdentityPermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to update more privileged identity",
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
ProjectPermissionIdentityActions.GrantPrivileges,
|
||||
ProjectPermissionSub.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
|
||||
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||
slug,
|
||||
projectMembershipId: identityProjectMembership.id
|
||||
});
|
||||
if (!identityPrivilege) {
|
||||
throw new NotFoundError({
|
||||
message: `Identity additional privilege with slug '${slug}' not found for the specified identity with ID '${identityProjectMembership.identityId}'`
|
||||
});
|
||||
}
|
||||
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 of provided slug exist" });
|
||||
}
|
||||
|
||||
const isTemporary = typeof data?.isTemporary !== "undefined" ? data.isTemporary : identityPrivilege.isTemporary;
|
||||
validateHandlebarTemplate("Identity Additional Privilege Update", JSON.stringify(data.permissions || []), {
|
||||
allowedExpressions: (val) => val.includes("identity.")
|
||||
});
|
||||
|
||||
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 || ""))
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
|
||||
|
||||
return {
|
||||
...additionalPrivilege,
|
||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||
};
|
||||
};
|
||||
|
||||
const deleteBySlug = async ({
|
||||
actorId,
|
||||
slug,
|
||||
identityId,
|
||||
projectSlug,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod
|
||||
}: TDeleteIdentityPrivilegeDTO) => {
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' 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, membership } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
actorId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Edit,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
|
||||
const { permission: identityRolePermission } = await permissionService.getProjectPermission({
|
||||
actor: ActorType.IDENTITY,
|
||||
actorId: identityProjectMembership.identityId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
ProjectPermissionIdentityActions.GrantPrivileges,
|
||||
ProjectPermissionSub.Identity,
|
||||
permission,
|
||||
identityRolePermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to edit more privileged identity",
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
ProjectPermissionIdentityActions.GrantPrivileges,
|
||||
ProjectPermissionSub.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
|
||||
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||
slug,
|
||||
projectMembershipId: identityProjectMembership.id
|
||||
});
|
||||
if (!identityPrivilege) {
|
||||
throw new NotFoundError({
|
||||
message: `Identity additional privilege with slug '${slug}' not found for the specified identity with ID '${identityProjectMembership.identityId}'`
|
||||
});
|
||||
}
|
||||
|
||||
const deletedPrivilege = await identityProjectAdditionalPrivilegeDAL.deleteById(identityPrivilege.id);
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(identityProjectMembership.projectId);
|
||||
|
||||
return {
|
||||
...deletedPrivilege,
|
||||
permissions: unpackPermissions(deletedPrivilege.permissions)
|
||||
};
|
||||
};
|
||||
|
||||
const getPrivilegeDetailsBySlug = async ({
|
||||
projectSlug,
|
||||
identityId,
|
||||
slug,
|
||||
actorOrgId,
|
||||
actor,
|
||||
actorId,
|
||||
actorAuthMethod
|
||||
}: TGetIdentityPrivilegeDetailsDTO) => {
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' 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(
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
|
||||
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||
slug,
|
||||
projectMembershipId: identityProjectMembership.id
|
||||
});
|
||||
if (!identityPrivilege) {
|
||||
throw new NotFoundError({
|
||||
message: `Identity additional privilege with slug '${slug}' not found for the specified identity with ID '${identityProjectMembership.identityId}'`
|
||||
});
|
||||
}
|
||||
return {
|
||||
...identityPrivilege,
|
||||
permissions: unpackPermissions(identityPrivilege.permissions)
|
||||
};
|
||||
};
|
||||
|
||||
const listIdentityProjectPrivileges = async ({
|
||||
identityId,
|
||||
actorOrgId,
|
||||
actor,
|
||||
actorId,
|
||||
actorAuthMethod,
|
||||
projectSlug
|
||||
}: TListIdentityPrivilegesDTO) => {
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' 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(
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
subject(ProjectPermissionSub.Identity, { identityId })
|
||||
);
|
||||
|
||||
const identityPrivileges = await identityProjectAdditionalPrivilegeDAL.find({
|
||||
projectMembershipId: identityProjectMembership.id
|
||||
});
|
||||
return identityPrivileges.map((el) => ({
|
||||
...el,
|
||||
permissions: unpackPermissions(el.permissions)
|
||||
}));
|
||||
};
|
||||
|
||||
return {
|
||||
create,
|
||||
updateBySlug,
|
||||
deleteBySlug,
|
||||
getPrivilegeDetailsBySlug,
|
||||
listIdentityProjectPrivileges
|
||||
};
|
||||
};
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
import { TProjectPermission } from "@app/lib/types";
|
||||
|
||||
import { TProjectPermissionV2Schema } from "../permission/project-permission";
|
||||
|
||||
export enum IdentityProjectAdditionalPrivilegeTemporaryMode {
|
||||
Relative = "relative"
|
||||
}
|
||||
|
||||
export type TCreateIdentityPrivilegeDTO = {
|
||||
permissions: TProjectPermissionV2Schema[];
|
||||
identityId: string;
|
||||
projectSlug: string;
|
||||
slug: string;
|
||||
} & (
|
||||
| {
|
||||
isTemporary: false;
|
||||
}
|
||||
| {
|
||||
isTemporary: true;
|
||||
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative;
|
||||
temporaryRange: string;
|
||||
temporaryAccessStartTime: string;
|
||||
}
|
||||
) &
|
||||
Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TUpdateIdentityPrivilegeDTO = { slug: string; identityId: string; projectSlug: string } & Omit<
|
||||
TProjectPermission,
|
||||
"projectId"
|
||||
> & {
|
||||
data: Partial<{
|
||||
permissions: TProjectPermissionV2Schema[];
|
||||
slug: string;
|
||||
isTemporary: boolean;
|
||||
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative;
|
||||
temporaryRange: string;
|
||||
temporaryAccessStartTime: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
export type TDeleteIdentityPrivilegeDTO = Omit<TProjectPermission, "projectId"> & {
|
||||
slug: string;
|
||||
identityId: string;
|
||||
projectSlug: string;
|
||||
};
|
||||
|
||||
export type TGetIdentityPrivilegeDetailsDTO = Omit<TProjectPermission, "projectId"> & {
|
||||
slug: string;
|
||||
identityId: string;
|
||||
projectSlug: string;
|
||||
};
|
||||
|
||||
export type TListIdentityPrivilegesDTO = Omit<TProjectPermission, "projectId"> & {
|
||||
identityId: string;
|
||||
projectSlug: string;
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { OrgMembershipStatus, TableName, TLdapConfigsUpdate, TUsers } from "@app/db/schemas";
|
||||
import { AccessScope, OrgMembershipStatus, TableName, TLdapConfigsUpdate, TUsers } from "@app/db/schemas";
|
||||
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
||||
import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns";
|
||||
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
||||
@@ -12,12 +12,12 @@ import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/
|
||||
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
|
||||
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
||||
import { TokenType } from "@app/services/auth-token/auth-token-types";
|
||||
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||
import { TMembershipRoleDALFactory } from "@app/services/membership/membership-role-dal";
|
||||
import { TMembershipGroupDALFactory } from "@app/services/membership-group/membership-group-dal";
|
||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||
import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns";
|
||||
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
||||
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
|
||||
@@ -49,13 +49,13 @@ import { TLdapGroupMapDALFactory } from "./ldap-group-map-dal";
|
||||
type TLdapConfigServiceFactoryDep = {
|
||||
ldapConfigDAL: Pick<TLdapConfigDALFactory, "create" | "update" | "findOne" | "transaction">;
|
||||
ldapGroupMapDAL: Pick<TLdapGroupMapDALFactory, "find" | "create" | "delete" | "findLdapGroupMapsByLdapConfigId">;
|
||||
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "create">;
|
||||
orgDAL: Pick<
|
||||
TOrgDALFactory,
|
||||
"createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById"
|
||||
>;
|
||||
groupDAL: Pick<TGroupDALFactory, "find" | "findOne">;
|
||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
||||
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "find">;
|
||||
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "create">;
|
||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany" | "delete">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findById">;
|
||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||
@@ -87,9 +87,9 @@ export const ldapConfigServiceFactory = ({
|
||||
ldapConfigDAL,
|
||||
ldapGroupMapDAL,
|
||||
orgDAL,
|
||||
orgMembershipDAL,
|
||||
groupDAL,
|
||||
groupProjectDAL,
|
||||
membershipGroupDAL,
|
||||
membershipRoleDAL,
|
||||
projectKeyDAL,
|
||||
projectDAL,
|
||||
projectBotDAL,
|
||||
@@ -388,25 +388,33 @@ export const ldapConfigServiceFactory = ({
|
||||
await userDAL.transaction(async (tx) => {
|
||||
const [orgMembership] = await orgDAL.findMembership(
|
||||
{
|
||||
[`${TableName.OrgMembership}.userId` as "userId"]: userAlias.userId,
|
||||
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
|
||||
[`${TableName.Membership}.actorUserId` as "actorUserId"]: userAlias.userId,
|
||||
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
|
||||
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
|
||||
},
|
||||
{ tx }
|
||||
);
|
||||
if (!orgMembership) {
|
||||
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
|
||||
|
||||
await orgDAL.createMembership(
|
||||
const membership = await orgDAL.createMembership(
|
||||
{
|
||||
userId: userAlias.userId,
|
||||
orgId,
|
||||
role,
|
||||
roleId,
|
||||
actorUserId: userAlias.userId,
|
||||
scopeOrgId: orgId,
|
||||
scope: AccessScope.Organization,
|
||||
status: OrgMembershipStatus.Accepted,
|
||||
isActive: true
|
||||
},
|
||||
tx
|
||||
);
|
||||
await membershipRoleDAL.create(
|
||||
{
|
||||
membershipId: membership.id,
|
||||
role,
|
||||
customRoleId: roleId
|
||||
},
|
||||
tx
|
||||
);
|
||||
} else if (orgMembership.status === OrgMembershipStatus.Invited) {
|
||||
await orgDAL.updateMembershipById(
|
||||
orgMembership.id,
|
||||
@@ -459,8 +467,9 @@ export const ldapConfigServiceFactory = ({
|
||||
|
||||
const [orgMembership] = await orgDAL.findMembership(
|
||||
{
|
||||
[`${TableName.OrgMembership}.userId` as "userId"]: newUser.id,
|
||||
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
|
||||
[`${TableName.Membership}.actorUserId` as "actorUserId"]: userAlias.userId,
|
||||
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
|
||||
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
|
||||
},
|
||||
{ tx }
|
||||
);
|
||||
@@ -469,16 +478,22 @@ export const ldapConfigServiceFactory = ({
|
||||
await throwOnPlanSeatLimitReached(licenseService, orgId, UserAliasType.LDAP);
|
||||
|
||||
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
|
||||
|
||||
await orgMembershipDAL.create(
|
||||
const membership = await orgDAL.createMembership(
|
||||
{
|
||||
userId: newUser.id,
|
||||
inviteEmail: email.toLowerCase(),
|
||||
orgId,
|
||||
role,
|
||||
roleId,
|
||||
actorUserId: newUser.id,
|
||||
scopeOrgId: orgId,
|
||||
scope: AccessScope.Organization,
|
||||
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
||||
isActive: true
|
||||
isActive: true,
|
||||
inviteEmail: email.toLowerCase()
|
||||
},
|
||||
tx
|
||||
);
|
||||
await membershipRoleDAL.create(
|
||||
{
|
||||
membershipId: membership.id,
|
||||
role,
|
||||
customRoleId: roleId
|
||||
},
|
||||
tx
|
||||
);
|
||||
@@ -542,10 +557,10 @@ export const ldapConfigServiceFactory = ({
|
||||
userDAL,
|
||||
userGroupMembershipDAL,
|
||||
orgDAL,
|
||||
groupProjectDAL,
|
||||
projectKeyDAL,
|
||||
projectDAL,
|
||||
projectBotDAL,
|
||||
membershipGroupDAL,
|
||||
tx
|
||||
});
|
||||
}
|
||||
@@ -566,7 +581,7 @@ export const ldapConfigServiceFactory = ({
|
||||
userIds: [newUser.id],
|
||||
userDAL,
|
||||
userGroupMembershipDAL,
|
||||
groupProjectDAL,
|
||||
membershipGroupDAL,
|
||||
projectKeyDAL,
|
||||
tx
|
||||
});
|
||||
|
||||
@@ -488,7 +488,7 @@ export const licenseServiceFactory = ({
|
||||
const getUsageMetrics = async (orgId: string) => {
|
||||
const [orgMembersUsed, identityUsed, projectCount] = await Promise.all([
|
||||
orgDAL.countAllOrgMembers(orgId),
|
||||
identityOrgMembershipDAL.countAllOrgIdentities({ orgId }),
|
||||
identityOrgMembershipDAL.countAllOrgIdentities({ scopeOrgId: orgId }),
|
||||
projectDAL.countOfOrgProjects(orgId)
|
||||
]);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { Issuer, Issuer as OpenIdIssuer, Strategy as OpenIdStrategy, TokenSet } from "openid-client";
|
||||
|
||||
import { OrgMembershipStatus, TableName, TUsers } from "@app/db/schemas";
|
||||
import { AccessScope, OrgMembershipStatus, TableName, TUsers } from "@app/db/schemas";
|
||||
import { TOidcConfigsUpdate } from "@app/db/schemas/oidc-configs";
|
||||
import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
||||
@@ -19,12 +19,12 @@ import { OrgServiceActor } from "@app/lib/types";
|
||||
import { ActorType, AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
|
||||
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
||||
import { TokenType } from "@app/services/auth-token/auth-token-types";
|
||||
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||
import { TMembershipRoleDALFactory } from "@app/services/membership/membership-role-dal";
|
||||
import { TMembershipGroupDALFactory } from "@app/services/membership-group/membership-group-dal";
|
||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||
import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns";
|
||||
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
||||
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
|
||||
@@ -62,7 +62,8 @@ type TOidcConfigServiceFactoryDep = {
|
||||
TOrgDALFactory,
|
||||
"createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById"
|
||||
>;
|
||||
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "create">;
|
||||
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "find">;
|
||||
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "create">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
|
||||
tokenService: Pick<TAuthTokenServiceFactory, "createTokenForUser">;
|
||||
smtpService: Pick<TSmtpService, "sendMail" | "verify">;
|
||||
@@ -78,7 +79,6 @@ type TOidcConfigServiceFactoryDep = {
|
||||
| "delete"
|
||||
| "filterProjectsByUserMembership"
|
||||
>;
|
||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany" | "delete">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findById">;
|
||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||
@@ -90,7 +90,6 @@ export type TOidcConfigServiceFactory = ReturnType<typeof oidcConfigServiceFacto
|
||||
|
||||
export const oidcConfigServiceFactory = ({
|
||||
orgDAL,
|
||||
orgMembershipDAL,
|
||||
userDAL,
|
||||
userAliasDAL,
|
||||
licenseService,
|
||||
@@ -100,7 +99,8 @@ export const oidcConfigServiceFactory = ({
|
||||
oidcConfigDAL,
|
||||
userGroupMembershipDAL,
|
||||
groupDAL,
|
||||
groupProjectDAL,
|
||||
membershipGroupDAL,
|
||||
membershipRoleDAL,
|
||||
projectKeyDAL,
|
||||
projectDAL,
|
||||
projectBotDAL,
|
||||
@@ -196,26 +196,33 @@ export const oidcConfigServiceFactory = ({
|
||||
const foundUser = await userDAL.findById(userAlias.userId, tx);
|
||||
const [orgMembership] = await orgDAL.findMembership(
|
||||
{
|
||||
[`${TableName.OrgMembership}.userId` as "userId"]: foundUser.id,
|
||||
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
|
||||
[`${TableName.Membership}.actorUserId` as "actorUserId"]: userAlias.userId,
|
||||
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
|
||||
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
|
||||
},
|
||||
{ tx }
|
||||
);
|
||||
if (!orgMembership) {
|
||||
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
|
||||
|
||||
await orgMembershipDAL.create(
|
||||
const membership = await orgDAL.createMembership(
|
||||
{
|
||||
userId: userAlias.userId,
|
||||
inviteEmail: email,
|
||||
orgId,
|
||||
role,
|
||||
roleId,
|
||||
status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
||||
actorUserId: userAlias.userId,
|
||||
scopeOrgId: orgId,
|
||||
scope: AccessScope.Organization,
|
||||
status: OrgMembershipStatus.Accepted,
|
||||
isActive: true
|
||||
},
|
||||
tx
|
||||
);
|
||||
await membershipRoleDAL.create(
|
||||
{
|
||||
membershipId: membership.id,
|
||||
role,
|
||||
customRoleId: roleId
|
||||
},
|
||||
tx
|
||||
);
|
||||
// Only update the membership to Accepted if the user account is already completed.
|
||||
} else if (orgMembership.status === OrgMembershipStatus.Invited && foundUser.isAccepted) {
|
||||
await orgDAL.updateMembershipById(
|
||||
@@ -288,8 +295,9 @@ export const oidcConfigServiceFactory = ({
|
||||
|
||||
const [orgMembership] = await orgDAL.findMembership(
|
||||
{
|
||||
[`${TableName.OrgMembership}.userId` as "userId"]: newUser.id,
|
||||
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
|
||||
[`${TableName.Membership}.actorUserId` as "actorUserId"]: userAlias.userId,
|
||||
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
|
||||
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
|
||||
},
|
||||
{ tx }
|
||||
);
|
||||
@@ -299,15 +307,22 @@ export const oidcConfigServiceFactory = ({
|
||||
|
||||
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
|
||||
|
||||
await orgMembershipDAL.create(
|
||||
const membership = await orgDAL.createMembership(
|
||||
{
|
||||
userId: newUser.id,
|
||||
inviteEmail: email,
|
||||
orgId,
|
||||
role,
|
||||
roleId,
|
||||
actorUserId: newUser.id,
|
||||
scopeOrgId: orgId,
|
||||
scope: AccessScope.Organization,
|
||||
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
||||
isActive: true
|
||||
isActive: true,
|
||||
inviteEmail: email.toLowerCase()
|
||||
},
|
||||
tx
|
||||
);
|
||||
await membershipRoleDAL.create(
|
||||
{
|
||||
membershipId: membership.id,
|
||||
role,
|
||||
customRoleId: roleId
|
||||
},
|
||||
tx
|
||||
);
|
||||
@@ -341,7 +356,7 @@ export const oidcConfigServiceFactory = ({
|
||||
userDAL,
|
||||
userGroupMembershipDAL,
|
||||
orgDAL,
|
||||
groupProjectDAL,
|
||||
membershipGroupDAL,
|
||||
projectKeyDAL,
|
||||
projectDAL,
|
||||
projectBotDAL
|
||||
@@ -378,7 +393,7 @@ export const oidcConfigServiceFactory = ({
|
||||
group,
|
||||
userDAL,
|
||||
userGroupMembershipDAL,
|
||||
groupProjectDAL,
|
||||
membershipGroupDAL,
|
||||
projectKeyDAL
|
||||
});
|
||||
}
|
||||
|
||||
-389
@@ -1,389 +0,0 @@
|
||||
import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
|
||||
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
||||
|
||||
import { ActionProjectType, TableName } from "@app/db/schemas";
|
||||
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
|
||||
import { ms } from "@app/lib/ms";
|
||||
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
|
||||
import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission";
|
||||
import { ActorType } from "@app/services/auth/auth-type";
|
||||
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
||||
|
||||
import { TAccessApprovalRequestDALFactory } from "../access-approval-request/access-approval-request-dal";
|
||||
import { constructPermissionErrorMessage, validatePrivilegeChangeOperation } from "../permission/permission-fns";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
||||
import {
|
||||
ProjectPermissionMemberActions,
|
||||
ProjectPermissionSet,
|
||||
ProjectPermissionSub
|
||||
} from "../permission/project-permission";
|
||||
import { ApprovalStatus } from "../secret-approval-request/secret-approval-request-types";
|
||||
import { TProjectUserAdditionalPrivilegeDALFactory } from "./project-user-additional-privilege-dal";
|
||||
import {
|
||||
ProjectUserAdditionalPrivilegeTemporaryMode,
|
||||
TProjectUserAdditionalPrivilegeServiceFactory
|
||||
} from "./project-user-additional-privilege-types";
|
||||
|
||||
type TProjectUserAdditionalPrivilegeServiceFactoryDep = {
|
||||
projectUserAdditionalPrivilegeDAL: TProjectUserAdditionalPrivilegeDALFactory;
|
||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findById" | "findOne">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "invalidateProjectPermissionCache">;
|
||||
accessApprovalRequestDAL: Pick<TAccessApprovalRequestDALFactory, "update">;
|
||||
};
|
||||
|
||||
const unpackPermissions = (permissions: unknown) =>
|
||||
UnpackedPermissionSchema.array().parse(
|
||||
unpackRules((permissions || []) as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[])
|
||||
);
|
||||
|
||||
export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
projectUserAdditionalPrivilegeDAL,
|
||||
projectMembershipDAL,
|
||||
permissionService,
|
||||
accessApprovalRequestDAL
|
||||
}: TProjectUserAdditionalPrivilegeServiceFactoryDep): TProjectUserAdditionalPrivilegeServiceFactory => {
|
||||
const create: TProjectUserAdditionalPrivilegeServiceFactory["create"] = async ({
|
||||
slug,
|
||||
actor,
|
||||
actorId,
|
||||
permissions: customPermission,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
projectMembershipId,
|
||||
...dto
|
||||
}) => {
|
||||
const projectMembership = await projectMembershipDAL.findById(projectMembershipId);
|
||||
if (!projectMembership)
|
||||
throw new NotFoundError({ message: `Project membership with ID ${projectMembershipId} found` });
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
actorId,
|
||||
projectId: projectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
|
||||
const { permission: targetUserPermission, membership } = await permissionService.getProjectPermission({
|
||||
actor: ActorType.USER,
|
||||
actorId: projectMembership.userId,
|
||||
projectId: projectMembership.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
|
||||
targetUserPermission.update(targetUserPermission.rules.concat(customPermission));
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
ProjectPermissionMemberActions.GrantPrivileges,
|
||||
ProjectPermissionSub.Member,
|
||||
permission,
|
||||
targetUserPermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to update more privileged user",
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
ProjectPermissionMemberActions.GrantPrivileges,
|
||||
ProjectPermissionSub.Member
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
|
||||
const existingSlug = await projectUserAdditionalPrivilegeDAL.findOne({
|
||||
slug,
|
||||
projectId: projectMembership.projectId,
|
||||
userId: projectMembership.userId
|
||||
});
|
||||
if (existingSlug)
|
||||
throw new BadRequestError({ message: `Additional privilege with provided slug ${slug} already exists` });
|
||||
|
||||
validateHandlebarTemplate("User Additional Privilege Create", JSON.stringify(customPermission || []), {
|
||||
allowedExpressions: (val) => val.includes("identity.")
|
||||
});
|
||||
|
||||
const packedPermission = JSON.stringify(packRules(customPermission));
|
||||
if (!dto.isTemporary) {
|
||||
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.create({
|
||||
userId: projectMembership.userId,
|
||||
projectId: projectMembership.projectId,
|
||||
slug,
|
||||
permissions: packedPermission
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(projectMembership.projectId);
|
||||
|
||||
return {
|
||||
...additionalPrivilege,
|
||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||
};
|
||||
}
|
||||
|
||||
const relativeTempAllocatedTimeInMs = ms(dto.temporaryRange);
|
||||
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.create({
|
||||
projectId: projectMembership.projectId,
|
||||
userId: projectMembership.userId,
|
||||
slug,
|
||||
permissions: packedPermission,
|
||||
isTemporary: true,
|
||||
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative,
|
||||
temporaryRange: dto.temporaryRange,
|
||||
temporaryAccessStartTime: new Date(dto.temporaryAccessStartTime),
|
||||
temporaryAccessEndTime: new Date(new Date(dto.temporaryAccessStartTime).getTime() + relativeTempAllocatedTimeInMs)
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(projectMembership.projectId);
|
||||
|
||||
return {
|
||||
...additionalPrivilege,
|
||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||
};
|
||||
};
|
||||
|
||||
const updateById: TProjectUserAdditionalPrivilegeServiceFactory["updateById"] = async ({
|
||||
privilegeId,
|
||||
actorOrgId,
|
||||
actor,
|
||||
actorId,
|
||||
actorAuthMethod,
|
||||
...dto
|
||||
}) => {
|
||||
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
||||
if (!userPrivilege)
|
||||
throw new NotFoundError({ message: `User additional privilege with ID ${privilegeId} not found` });
|
||||
|
||||
const projectMembership = await projectMembershipDAL.findOne({
|
||||
userId: userPrivilege.userId,
|
||||
projectId: userPrivilege.projectId
|
||||
});
|
||||
|
||||
if (!projectMembership)
|
||||
throw new NotFoundError({
|
||||
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
|
||||
});
|
||||
|
||||
const { permission, membership } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
actorId,
|
||||
projectId: projectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
|
||||
const { permission: targetUserPermission } = await permissionService.getProjectPermission({
|
||||
actor: ActorType.USER,
|
||||
actorId: projectMembership.userId,
|
||||
projectId: projectMembership.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
|
||||
targetUserPermission.update(targetUserPermission.rules.concat(dto.permissions || []));
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
ProjectPermissionMemberActions.GrantPrivileges,
|
||||
ProjectPermissionSub.Member,
|
||||
permission,
|
||||
targetUserPermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to update more privileged user",
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
ProjectPermissionMemberActions.GrantPrivileges,
|
||||
ProjectPermissionSub.Member
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
|
||||
if (dto?.slug) {
|
||||
const existingSlug = await projectUserAdditionalPrivilegeDAL.findOne({
|
||||
slug: dto.slug,
|
||||
userId: projectMembership.id,
|
||||
projectId: projectMembership.projectId
|
||||
});
|
||||
if (existingSlug && existingSlug.id !== userPrivilege.id)
|
||||
throw new BadRequestError({ message: `Additional privilege with provided slug ${dto.slug} already exists` });
|
||||
}
|
||||
|
||||
validateHandlebarTemplate("User Additional Privilege Update", JSON.stringify(dto.permissions || []), {
|
||||
allowedExpressions: (val) => val.includes("identity.")
|
||||
});
|
||||
|
||||
const isTemporary = typeof dto?.isTemporary !== "undefined" ? dto.isTemporary : userPrivilege.isTemporary;
|
||||
|
||||
const packedPermission = dto.permissions && JSON.stringify(packRules(dto.permissions));
|
||||
if (isTemporary) {
|
||||
const temporaryAccessStartTime = dto?.temporaryAccessStartTime || userPrivilege?.temporaryAccessStartTime;
|
||||
const temporaryRange = dto?.temporaryRange || userPrivilege?.temporaryRange;
|
||||
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.updateById(userPrivilege.id, {
|
||||
slug: dto.slug,
|
||||
permissions: packedPermission,
|
||||
isTemporary: dto.isTemporary,
|
||||
temporaryRange: dto.temporaryRange,
|
||||
temporaryMode: dto.temporaryMode,
|
||||
temporaryAccessStartTime: new Date(temporaryAccessStartTime || ""),
|
||||
temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || ""))
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(projectMembership.projectId);
|
||||
|
||||
return {
|
||||
...additionalPrivilege,
|
||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||
};
|
||||
}
|
||||
|
||||
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.updateById(userPrivilege.id, {
|
||||
slug: dto.slug,
|
||||
permissions: packedPermission,
|
||||
isTemporary: false,
|
||||
temporaryAccessStartTime: null,
|
||||
temporaryAccessEndTime: null,
|
||||
temporaryRange: null,
|
||||
temporaryMode: null
|
||||
});
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(projectMembership.projectId);
|
||||
|
||||
return {
|
||||
...additionalPrivilege,
|
||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||
};
|
||||
};
|
||||
|
||||
const deleteById: TProjectUserAdditionalPrivilegeServiceFactory["deleteById"] = async ({
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
privilegeId
|
||||
}) => {
|
||||
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
||||
if (!userPrivilege)
|
||||
throw new NotFoundError({ message: `User additional privilege with ID ${privilegeId} not found` });
|
||||
|
||||
const projectMembership = await projectMembershipDAL.findOne({
|
||||
userId: userPrivilege.userId,
|
||||
projectId: userPrivilege.projectId
|
||||
});
|
||||
if (!projectMembership)
|
||||
throw new NotFoundError({
|
||||
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
|
||||
});
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
actorId,
|
||||
projectId: projectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
|
||||
|
||||
// TODO(simp): look into this in the new ap service
|
||||
await accessApprovalRequestDAL.update(
|
||||
{
|
||||
privilegeId: userPrivilege.id
|
||||
},
|
||||
{
|
||||
privilegeDeletedAt: new Date(),
|
||||
status: ApprovalStatus.REJECTED
|
||||
}
|
||||
);
|
||||
const deletedPrivilege = await projectUserAdditionalPrivilegeDAL.deleteById(userPrivilege.id);
|
||||
|
||||
await permissionService.invalidateProjectPermissionCache(projectMembership.projectId);
|
||||
|
||||
return {
|
||||
...deletedPrivilege,
|
||||
permissions: unpackPermissions(deletedPrivilege.permissions)
|
||||
};
|
||||
};
|
||||
|
||||
const getPrivilegeDetailsById: TProjectUserAdditionalPrivilegeServiceFactory["getPrivilegeDetailsById"] = async ({
|
||||
privilegeId,
|
||||
actorOrgId,
|
||||
actor,
|
||||
actorId,
|
||||
actorAuthMethod
|
||||
}) => {
|
||||
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
||||
if (!userPrivilege)
|
||||
throw new NotFoundError({ message: `User additional privilege with ID ${privilegeId} not found` });
|
||||
|
||||
const projectMembership = await projectMembershipDAL.findOne({
|
||||
userId: userPrivilege.userId,
|
||||
projectId: userPrivilege.projectId
|
||||
});
|
||||
if (!projectMembership)
|
||||
throw new NotFoundError({
|
||||
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
|
||||
});
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
actorId,
|
||||
projectId: projectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
|
||||
|
||||
return {
|
||||
...userPrivilege,
|
||||
permissions: unpackPermissions(userPrivilege.permissions)
|
||||
};
|
||||
};
|
||||
|
||||
const listPrivileges: TProjectUserAdditionalPrivilegeServiceFactory["listPrivileges"] = async ({
|
||||
projectMembershipId,
|
||||
actorOrgId,
|
||||
actor,
|
||||
actorId,
|
||||
actorAuthMethod
|
||||
}) => {
|
||||
const projectMembership = await projectMembershipDAL.findById(projectMembershipId);
|
||||
if (!projectMembership)
|
||||
throw new NotFoundError({ message: `Project membership with ID ${projectMembershipId} not found` });
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
actorId,
|
||||
projectId: projectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
|
||||
|
||||
const userPrivileges = await projectUserAdditionalPrivilegeDAL.find(
|
||||
{
|
||||
userId: projectMembership.userId,
|
||||
projectId: projectMembership.projectId
|
||||
},
|
||||
{ sort: [[`${TableName.ProjectUserAdditionalPrivilege}.slug` as "slug", "asc"]] }
|
||||
);
|
||||
return userPrivileges;
|
||||
};
|
||||
|
||||
return {
|
||||
create,
|
||||
updateById,
|
||||
deleteById,
|
||||
getPrivilegeDetailsById,
|
||||
listPrivileges
|
||||
};
|
||||
};
|
||||
-60
@@ -1,60 +0,0 @@
|
||||
import { TProjectUserAdditionalPrivilege } from "@app/db/schemas";
|
||||
import { TProjectPermission } from "@app/lib/types";
|
||||
|
||||
import { TProjectPermissionV2Schema } from "../permission/project-permission";
|
||||
|
||||
export enum ProjectUserAdditionalPrivilegeTemporaryMode {
|
||||
Relative = "relative"
|
||||
}
|
||||
|
||||
export type TCreateUserPrivilegeDTO = (
|
||||
| {
|
||||
permissions: TProjectPermissionV2Schema[];
|
||||
projectMembershipId: string;
|
||||
slug: string;
|
||||
isTemporary: false;
|
||||
}
|
||||
| {
|
||||
permissions: TProjectPermissionV2Schema[];
|
||||
projectMembershipId: string;
|
||||
slug: string;
|
||||
isTemporary: true;
|
||||
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative;
|
||||
temporaryRange: string;
|
||||
temporaryAccessStartTime: string;
|
||||
}
|
||||
) &
|
||||
Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TUpdateUserPrivilegeDTO = { privilegeId: string } & Omit<TProjectPermission, "projectId"> &
|
||||
Partial<{
|
||||
permissions: TProjectPermissionV2Schema[];
|
||||
slug: string;
|
||||
isTemporary: boolean;
|
||||
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative;
|
||||
temporaryRange: string;
|
||||
temporaryAccessStartTime: string;
|
||||
}>;
|
||||
|
||||
export type TDeleteUserPrivilegeDTO = Omit<TProjectPermission, "projectId"> & { privilegeId: string };
|
||||
|
||||
export type TGetUserPrivilegeDetailsDTO = Omit<TProjectPermission, "projectId"> & { privilegeId: string };
|
||||
|
||||
export type TListUserPrivilegesDTO = Omit<TProjectPermission, "projectId"> & { projectMembershipId: string };
|
||||
|
||||
interface TAdditionalPrivilege extends TProjectUserAdditionalPrivilege {
|
||||
permissions: {
|
||||
action: string[];
|
||||
subject?: string | undefined;
|
||||
conditions?: unknown;
|
||||
inverted?: boolean | undefined;
|
||||
}[];
|
||||
}
|
||||
|
||||
export type TProjectUserAdditionalPrivilegeServiceFactory = {
|
||||
create: (arg: TCreateUserPrivilegeDTO) => Promise<TAdditionalPrivilege>;
|
||||
updateById: (arg: TUpdateUserPrivilegeDTO) => Promise<TAdditionalPrivilege>;
|
||||
deleteById: (arg: TDeleteUserPrivilegeDTO) => Promise<TAdditionalPrivilege>;
|
||||
getPrivilegeDetailsById: (arg: TGetUserPrivilegeDetailsDTO) => Promise<TAdditionalPrivilege>;
|
||||
listPrivileges: (arg: TListUserPrivilegesDTO) => Promise<TProjectUserAdditionalPrivilege[]>;
|
||||
};
|
||||
@@ -10,7 +10,8 @@ import {
|
||||
TGroups,
|
||||
TSamlConfigs,
|
||||
TSamlConfigsUpdate,
|
||||
TUsers
|
||||
TUsers,
|
||||
AccessScope
|
||||
} from "@app/db/schemas";
|
||||
import { throwOnPlanSeatLimitReached } from "@app/ee/services/license/license-fns";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
@@ -23,12 +24,9 @@ import { TGroupProjectDALFactory } from "@app/services/group-project/group-proje
|
||||
import { TIdentityMetadataDALFactory } from "@app/services/identity/identity-metadata-dal";
|
||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||
import { TMembershipRoleDALFactory } from "@app/services/membership/membership-role-dal";
|
||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||
import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns";
|
||||
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
||||
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
|
||||
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
||||
import { LoginMethod } from "@app/services/super-admin/super-admin-types";
|
||||
@@ -68,16 +66,7 @@ type TSamlConfigServiceFactoryDep = {
|
||||
"createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById"
|
||||
>;
|
||||
identityMetadataDAL: Pick<TIdentityMetadataDALFactory, "delete" | "insertMany" | "transaction">;
|
||||
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "create">;
|
||||
groupDAL: Pick<TGroupDALFactory, "create" | "findOne" | "find" | "transaction">;
|
||||
userGroupMembershipDAL: Pick<
|
||||
TUserGroupMembershipDALFactory,
|
||||
"find" | "delete" | "transaction" | "insertMany" | "filterProjectsByUserMembership"
|
||||
>;
|
||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findById" | "findProjectGhostUser">;
|
||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete" | "findLatestProjectKey" | "insertMany">;
|
||||
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "create">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
|
||||
tokenService: Pick<TAuthTokenServiceFactory, "createTokenForUser">;
|
||||
@@ -88,7 +77,6 @@ type TSamlConfigServiceFactoryDep = {
|
||||
export const samlConfigServiceFactory = ({
|
||||
samlConfigDAL,
|
||||
orgDAL,
|
||||
orgMembershipDAL,
|
||||
userDAL,
|
||||
userAliasDAL,
|
||||
groupDAL,
|
||||
@@ -102,7 +90,8 @@ export const samlConfigServiceFactory = ({
|
||||
tokenService,
|
||||
smtpService,
|
||||
identityMetadataDAL,
|
||||
kmsService
|
||||
kmsService,
|
||||
membershipRoleDAL
|
||||
}: TSamlConfigServiceFactoryDep): TSamlConfigServiceFactory => {
|
||||
const parseSamlGroups = (groupsValue: string): string[] => {
|
||||
let samlGroups: string[] = [];
|
||||
@@ -506,26 +495,35 @@ export const samlConfigServiceFactory = ({
|
||||
const foundUser = await userDAL.findById(userAlias.userId, tx);
|
||||
const [orgMembership] = await orgDAL.findMembership(
|
||||
{
|
||||
[`${TableName.OrgMembership}.userId` as "userId"]: foundUser.id,
|
||||
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
|
||||
[`${TableName.Membership}.actorUserId` as "actorUserId"]: userAlias.userId,
|
||||
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
|
||||
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
|
||||
},
|
||||
{ tx }
|
||||
);
|
||||
|
||||
if (!orgMembership) {
|
||||
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
|
||||
|
||||
await orgMembershipDAL.create(
|
||||
const membership = await orgDAL.createMembership(
|
||||
{
|
||||
userId: userAlias.userId,
|
||||
actorUserId: userAlias.userId,
|
||||
inviteEmail: email,
|
||||
orgId,
|
||||
role,
|
||||
roleId,
|
||||
status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited,
|
||||
scopeOrgId: orgId,
|
||||
scope: AccessScope.Organization,
|
||||
status: OrgMembershipStatus.Accepted,
|
||||
isActive: true
|
||||
},
|
||||
tx
|
||||
);
|
||||
await membershipRoleDAL.create(
|
||||
{
|
||||
membershipId: membership.id,
|
||||
role,
|
||||
customRoleId: roleId
|
||||
},
|
||||
tx
|
||||
);
|
||||
// Only update the membership to Accepted if the user account is already completed.
|
||||
} else if (orgMembership.status === OrgMembershipStatus.Invited && foundUser.isAccepted) {
|
||||
await orgDAL.updateMembershipById(
|
||||
@@ -606,8 +604,9 @@ export const samlConfigServiceFactory = ({
|
||||
|
||||
const [orgMembership] = await orgDAL.findMembership(
|
||||
{
|
||||
[`${TableName.OrgMembership}.userId` as "userId"]: newUser.id,
|
||||
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
|
||||
[`${TableName.Membership}.actorUserId` as "actorUserId"]: userAlias.userId,
|
||||
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
|
||||
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
|
||||
},
|
||||
{ tx }
|
||||
);
|
||||
@@ -617,15 +616,22 @@ export const samlConfigServiceFactory = ({
|
||||
|
||||
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
|
||||
|
||||
await orgMembershipDAL.create(
|
||||
const membership = await orgDAL.createMembership(
|
||||
{
|
||||
userId: newUser.id,
|
||||
inviteEmail: email,
|
||||
orgId,
|
||||
role,
|
||||
roleId,
|
||||
actorUserId: newUser.id,
|
||||
scopeOrgId: orgId,
|
||||
scope: AccessScope.Organization,
|
||||
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
||||
isActive: true
|
||||
isActive: true,
|
||||
inviteEmail: email.toLowerCase()
|
||||
},
|
||||
tx
|
||||
);
|
||||
await membershipRoleDAL.create(
|
||||
{
|
||||
membershipId: membership.id,
|
||||
role,
|
||||
customRoleId: roleId
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
@@ -2,7 +2,15 @@ import { ForbiddenError } from "@casl/ability";
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
import { scimPatch } from "scim-patch";
|
||||
|
||||
import { OrgMembershipRole, OrgMembershipStatus, TableName, TGroups, TOrgMemberships, TUsers } from "@app/db/schemas";
|
||||
import {
|
||||
AccessScope,
|
||||
OrgMembershipRole,
|
||||
OrgMembershipStatus,
|
||||
TableName,
|
||||
TGroups,
|
||||
TMemberships,
|
||||
TUsers
|
||||
} from "@app/db/schemas";
|
||||
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
||||
import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns";
|
||||
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
||||
@@ -13,12 +21,13 @@ import { BadRequestError, NotFoundError, ScimRequestError, UnauthorizedError } f
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
import { AuthTokenType } from "@app/services/auth/auth-type";
|
||||
import { TExternalGroupOrgRoleMappingDALFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-dal";
|
||||
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
||||
import { TMembershipRoleDALFactory } from "@app/services/membership/membership-role-dal";
|
||||
import { TMembershipGroupDALFactory } from "@app/services/membership-group/membership-group-dal";
|
||||
import { TMembershipUserDALFactory } from "@app/services/membership-user/membership-user-dal";
|
||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||
import { deleteOrgMembershipFn } from "@app/services/org/org-fns";
|
||||
import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns";
|
||||
import { OrgAuthMethod } from "@app/services/org/org-types";
|
||||
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
||||
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
|
||||
@@ -33,7 +42,6 @@ import { UserAliasType } from "@app/services/user-alias/user-alias-types";
|
||||
import { TLicenseServiceFactory } from "../license/license-service";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
||||
import { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal";
|
||||
import { buildScimGroup, buildScimGroupList, buildScimUser, buildScimUserList, parseScimFilter } from "./scim-fns";
|
||||
import { TScimGroup, TScimServiceFactory } from "./scim-types";
|
||||
|
||||
@@ -55,12 +63,9 @@ type TScimServiceFactoryDep = {
|
||||
| "updateMembershipById"
|
||||
| "findOrgById"
|
||||
>;
|
||||
orgMembershipDAL: Pick<
|
||||
TOrgMembershipDALFactory,
|
||||
"find" | "findOne" | "create" | "updateById" | "findById" | "update"
|
||||
>;
|
||||
membershipUserDAL: TMembershipUserDALFactory;
|
||||
projectDAL: Pick<TProjectDALFactory, "find" | "findProjectGhostUser" | "findById">;
|
||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "delete" | "findProjectMembershipsByUserId">;
|
||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findProjectMembershipsByUserId">;
|
||||
groupDAL: Pick<
|
||||
TGroupDALFactory,
|
||||
| "create"
|
||||
@@ -72,7 +77,8 @@ type TScimServiceFactoryDep = {
|
||||
| "updateById"
|
||||
| "update"
|
||||
>;
|
||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
||||
membershipGroupDAL: Pick<TMembershipGroupDALFactory, "find">;
|
||||
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "create" | "update">;
|
||||
userGroupMembershipDAL: Pick<
|
||||
TUserGroupMembershipDALFactory,
|
||||
| "find"
|
||||
@@ -88,7 +94,6 @@ type TScimServiceFactoryDep = {
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
smtpService: Pick<TSmtpService, "sendMail">;
|
||||
projectUserAdditionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
|
||||
externalGroupOrgRoleMappingDAL: TExternalGroupOrgRoleMappingDALFactory;
|
||||
};
|
||||
|
||||
@@ -98,18 +103,18 @@ export const scimServiceFactory = ({
|
||||
userDAL,
|
||||
userAliasDAL,
|
||||
orgDAL,
|
||||
orgMembershipDAL,
|
||||
projectDAL,
|
||||
projectMembershipDAL,
|
||||
groupDAL,
|
||||
groupProjectDAL,
|
||||
userGroupMembershipDAL,
|
||||
projectKeyDAL,
|
||||
projectBotDAL,
|
||||
permissionService,
|
||||
projectUserAdditionalPrivilegeDAL,
|
||||
smtpService,
|
||||
externalGroupOrgRoleMappingDAL
|
||||
externalGroupOrgRoleMappingDAL,
|
||||
membershipGroupDAL,
|
||||
membershipUserDAL,
|
||||
membershipRoleDAL
|
||||
}: TScimServiceFactoryDep): TScimServiceFactory => {
|
||||
const createScimToken: TScimServiceFactory["createScimToken"] = async ({
|
||||
actor,
|
||||
@@ -244,8 +249,9 @@ export const scimServiceFactory = ({
|
||||
const getScimUser: TScimServiceFactory["getScimUser"] = async ({ orgMembershipId, orgId }) => {
|
||||
const [membership] = await orgDAL
|
||||
.findMembership({
|
||||
[`${TableName.OrgMembership}.id` as "id"]: orgMembershipId,
|
||||
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId
|
||||
[`${TableName.Membership}.id` as "id"]: orgMembershipId,
|
||||
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
|
||||
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
|
||||
})
|
||||
.catch(() => {
|
||||
throw new ScimRequestError({
|
||||
@@ -322,13 +328,14 @@ export const scimServiceFactory = ({
|
||||
|
||||
const { user: createdUser, orgMembership: createdOrgMembership } = await userDAL.transaction(async (tx) => {
|
||||
let user: TUsers | undefined;
|
||||
let orgMembership: TOrgMemberships;
|
||||
let orgMembership: TMemberships;
|
||||
if (userAlias) {
|
||||
user = await userDAL.findById(userAlias.userId, tx);
|
||||
orgMembership = await orgMembershipDAL.findOne(
|
||||
orgMembership = await membershipUserDAL.findOne(
|
||||
{
|
||||
userId: user.id,
|
||||
orgId
|
||||
actorUserId: user.id,
|
||||
scope: AccessScope.Organization,
|
||||
scopeOrgId: orgId
|
||||
},
|
||||
tx
|
||||
);
|
||||
@@ -336,20 +343,24 @@ export const scimServiceFactory = ({
|
||||
if (!orgMembership) {
|
||||
const { role, roleId } = await getDefaultOrgMembershipRole(org.defaultMembershipRole);
|
||||
|
||||
orgMembership = await orgMembershipDAL.create(
|
||||
orgMembership = await membershipUserDAL.create(
|
||||
{
|
||||
userId: userAlias.userId,
|
||||
actorUserId: userAlias.userId,
|
||||
inviteEmail: email.toLowerCase(),
|
||||
orgId,
|
||||
role,
|
||||
roleId,
|
||||
scopeOrgId: orgId,
|
||||
scope: AccessScope.Organization,
|
||||
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
||||
isActive: true
|
||||
},
|
||||
tx
|
||||
);
|
||||
await membershipRoleDAL.create({
|
||||
membershipId: orgMembership.id,
|
||||
role,
|
||||
customRoleId: roleId
|
||||
});
|
||||
} else if (orgMembership.status === OrgMembershipStatus.Invited && user.isAccepted) {
|
||||
orgMembership = await orgMembershipDAL.updateById(
|
||||
orgMembership = await membershipUserDAL.updateById(
|
||||
orgMembership.id,
|
||||
{
|
||||
status: OrgMembershipStatus.Accepted
|
||||
@@ -401,8 +412,9 @@ export const scimServiceFactory = ({
|
||||
|
||||
const [foundOrgMembership] = await orgDAL.findMembership(
|
||||
{
|
||||
[`${TableName.OrgMembership}.userId` as "userId"]: user.id,
|
||||
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
|
||||
[`${TableName.Membership}.actorUserId` as "actorUserId"]: user.id,
|
||||
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
|
||||
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
|
||||
},
|
||||
{ tx }
|
||||
);
|
||||
@@ -412,18 +424,25 @@ export const scimServiceFactory = ({
|
||||
if (!orgMembership) {
|
||||
const { role, roleId } = await getDefaultOrgMembershipRole(org.defaultMembershipRole);
|
||||
|
||||
orgMembership = await orgMembershipDAL.create(
|
||||
orgMembership = await membershipUserDAL.create(
|
||||
{
|
||||
userId: user.id,
|
||||
actorUserId: user.id,
|
||||
inviteEmail: email.toLowerCase(),
|
||||
orgId,
|
||||
role,
|
||||
roleId,
|
||||
scopeOrgId: orgId,
|
||||
scope: AccessScope.Organization,
|
||||
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
||||
isActive: true
|
||||
},
|
||||
tx
|
||||
);
|
||||
await membershipRoleDAL.create(
|
||||
{
|
||||
membershipId: orgMembership.id,
|
||||
role,
|
||||
customRoleId: roleId
|
||||
},
|
||||
tx
|
||||
);
|
||||
// Only update the membership to Accepted if the user account is already completed.
|
||||
} else if (orgMembership.status === OrgMembershipStatus.Invited && user.isAccepted) {
|
||||
orgMembership = await orgDAL.updateMembershipById(
|
||||
@@ -475,8 +494,9 @@ export const scimServiceFactory = ({
|
||||
|
||||
const [membership] = await orgDAL
|
||||
.findMembership({
|
||||
[`${TableName.OrgMembership}.id` as "id"]: orgMembershipId,
|
||||
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId
|
||||
[`${TableName.Membership}.id` as "id"]: orgMembershipId,
|
||||
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
|
||||
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
|
||||
})
|
||||
.catch(() => {
|
||||
throw new ScimRequestError({
|
||||
@@ -485,7 +505,7 @@ export const scimServiceFactory = ({
|
||||
});
|
||||
});
|
||||
|
||||
if (!membership)
|
||||
if (!membership || !membership.actorUserId)
|
||||
throw new ScimRequestError({
|
||||
detail: "User not found",
|
||||
status: 404
|
||||
@@ -514,7 +534,7 @@ export const scimServiceFactory = ({
|
||||
org.orgAuthMethod === OrgAuthMethod.OIDC ? serverCfg.trustOidcEmails : serverCfg.trustSamlEmails;
|
||||
|
||||
await userDAL.transaction(async (tx) => {
|
||||
await orgMembershipDAL.updateById(
|
||||
await membershipUserDAL.updateById(
|
||||
membership.id,
|
||||
{
|
||||
isActive: scimUser.active
|
||||
@@ -523,7 +543,7 @@ export const scimServiceFactory = ({
|
||||
);
|
||||
const hasEmailChanged = scimUser.emails[0].value !== membership.email;
|
||||
await userDAL.updateById(
|
||||
membership.userId,
|
||||
membership.actorUserId as string,
|
||||
{
|
||||
firstName: scimUser.name.givenName,
|
||||
email: scimUser.emails[0].value.toLowerCase(),
|
||||
@@ -556,8 +576,9 @@ export const scimServiceFactory = ({
|
||||
|
||||
const [membership] = await orgDAL
|
||||
.findMembership({
|
||||
[`${TableName.OrgMembership}.id` as "id"]: orgMembershipId,
|
||||
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId
|
||||
[`${TableName.Membership}.id` as "id"]: orgMembershipId,
|
||||
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
|
||||
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
|
||||
})
|
||||
.catch(() => {
|
||||
throw new ScimRequestError({
|
||||
@@ -566,7 +587,7 @@ export const scimServiceFactory = ({
|
||||
});
|
||||
});
|
||||
|
||||
if (!membership)
|
||||
if (!membership || !membership.actorUserId)
|
||||
throw new ScimRequestError({
|
||||
detail: "User not found",
|
||||
status: 404
|
||||
@@ -587,7 +608,7 @@ export const scimServiceFactory = ({
|
||||
{
|
||||
orgId,
|
||||
aliasType: org.orgAuthMethod === OrgAuthMethod.OIDC ? UserAliasType.OIDC : UserAliasType.SAML,
|
||||
userId: membership.userId
|
||||
userId: membership.actorUserId as string
|
||||
},
|
||||
{
|
||||
externalId
|
||||
@@ -595,7 +616,7 @@ export const scimServiceFactory = ({
|
||||
tx
|
||||
);
|
||||
|
||||
await orgMembershipDAL.updateById(
|
||||
await membershipUserDAL.updateById(
|
||||
membership.id,
|
||||
{
|
||||
isActive: active
|
||||
@@ -603,7 +624,7 @@ export const scimServiceFactory = ({
|
||||
tx
|
||||
);
|
||||
await userDAL.updateById(
|
||||
membership.userId,
|
||||
membership.actorUserId!,
|
||||
{
|
||||
firstName,
|
||||
email: email?.toLowerCase(),
|
||||
@@ -628,8 +649,9 @@ export const scimServiceFactory = ({
|
||||
|
||||
const deleteScimUser: TScimServiceFactory["deleteScimUser"] = async ({ orgMembershipId, orgId }) => {
|
||||
const [membership] = await orgDAL.findMembership({
|
||||
[`${TableName.OrgMembership}.id` as "id"]: orgMembershipId,
|
||||
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId
|
||||
[`${TableName.Membership}.id` as "id"]: orgMembershipId,
|
||||
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
|
||||
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization
|
||||
});
|
||||
|
||||
if (!membership)
|
||||
@@ -647,13 +669,13 @@ export const scimServiceFactory = ({
|
||||
|
||||
await deleteOrgMembershipFn({
|
||||
orgMembershipId: membership.id,
|
||||
orgId: membership.orgId,
|
||||
orgId: membership.scopeOrgId,
|
||||
orgDAL,
|
||||
projectMembershipDAL,
|
||||
projectUserAdditionalPrivilegeDAL,
|
||||
projectKeyDAL,
|
||||
userAliasDAL,
|
||||
licenseService
|
||||
licenseService,
|
||||
membershipUserDAL
|
||||
});
|
||||
|
||||
return {}; // intentionally return empty object upon success
|
||||
@@ -750,8 +772,9 @@ export const scimServiceFactory = ({
|
||||
if (!externalGroupMapping) return;
|
||||
|
||||
// only get org memberships that are new (invites)
|
||||
const newOrgMemberships = await orgMembershipDAL.find({
|
||||
const newOrgMemberships = await membershipUserDAL.find({
|
||||
status: "invited",
|
||||
scope: AccessScope.Organization,
|
||||
$in: {
|
||||
id: members.map((member) => member.value)
|
||||
}
|
||||
@@ -760,7 +783,7 @@ export const scimServiceFactory = ({
|
||||
if (!newOrgMemberships.length) return;
|
||||
|
||||
// set new membership roles to group mapping value
|
||||
await orgMembershipDAL.update(
|
||||
await membershipRoleDAL.update(
|
||||
{
|
||||
$in: {
|
||||
id: newOrgMemberships.map((membership) => membership.id)
|
||||
@@ -768,7 +791,7 @@ export const scimServiceFactory = ({
|
||||
},
|
||||
{
|
||||
role: externalGroupMapping.role,
|
||||
roleId: externalGroupMapping.roleId
|
||||
customRoleId: externalGroupMapping.roleId
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -822,7 +845,8 @@ export const scimServiceFactory = ({
|
||||
);
|
||||
|
||||
if (members && members.length) {
|
||||
const orgMemberships = await orgMembershipDAL.find({
|
||||
const orgMemberships = await membershipUserDAL.find({
|
||||
scope: AccessScope.Organization,
|
||||
$in: {
|
||||
id: members.map((member) => member.value)
|
||||
}
|
||||
@@ -830,14 +854,14 @@ export const scimServiceFactory = ({
|
||||
|
||||
const newMembers = await addUsersToGroupByUserIds({
|
||||
group,
|
||||
userIds: orgMemberships.map((membership) => membership.userId as string),
|
||||
userIds: orgMemberships.map((membership) => membership.actorUserId as string),
|
||||
userDAL,
|
||||
userGroupMembershipDAL,
|
||||
orgDAL,
|
||||
groupProjectDAL,
|
||||
projectKeyDAL,
|
||||
projectDAL,
|
||||
projectBotDAL,
|
||||
membershipGroupDAL,
|
||||
tx
|
||||
});
|
||||
|
||||
@@ -850,9 +874,10 @@ export const scimServiceFactory = ({
|
||||
});
|
||||
|
||||
const orgMemberships = await orgDAL.findMembership({
|
||||
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId,
|
||||
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
|
||||
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization,
|
||||
$in: {
|
||||
[`${TableName.OrgMembership}.userId` as "userId"]: newGroup.newMembers.map((member) => member.id)
|
||||
[`${TableName.Membership}.actorUserId` as "actorUserId"]: newGroup.newMembers.map((member) => member.id)
|
||||
}
|
||||
});
|
||||
|
||||
@@ -895,9 +920,10 @@ export const scimServiceFactory = ({
|
||||
.then((g) => g.members);
|
||||
|
||||
const orgMemberships = await orgDAL.findMembership({
|
||||
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId,
|
||||
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
|
||||
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization,
|
||||
$in: {
|
||||
[`${TableName.OrgMembership}.userId` as "userId"]: users
|
||||
[`${TableName.Membership}.actorUserId` as "actorUserId"]: users
|
||||
.filter((user) => user.isPartOfGroup)
|
||||
.map((user) => user.id)
|
||||
}
|
||||
@@ -933,10 +959,10 @@ export const scimServiceFactory = ({
|
||||
}
|
||||
|
||||
const updatedGroup = await groupDAL.transaction(async (tx) => {
|
||||
if (group.name !== displayName) {
|
||||
if (group?.name !== displayName) {
|
||||
await externalGroupOrgRoleMappingDAL.update(
|
||||
{
|
||||
groupName: group.name,
|
||||
groupName: group?.name,
|
||||
orgId
|
||||
},
|
||||
{
|
||||
@@ -958,14 +984,16 @@ export const scimServiceFactory = ({
|
||||
}
|
||||
|
||||
const orgMemberships = members.length
|
||||
? await orgMembershipDAL.find({
|
||||
? await membershipUserDAL.find({
|
||||
[`${TableName.Membership}.scopeOrgId` as "scopeOrgId"]: orgId,
|
||||
[`${TableName.Membership}.scope` as "scope"]: AccessScope.Organization,
|
||||
$in: {
|
||||
id: members.map((member) => member.value)
|
||||
}
|
||||
})
|
||||
: [];
|
||||
|
||||
const membersIdsSet = new Set(orgMemberships.map((orgMembership) => orgMembership.userId));
|
||||
const membersIdsSet = new Set(orgMemberships.map((orgMembership) => orgMembership.actorUserId as string));
|
||||
const userGroupMembers = await userGroupMembershipDAL.find({
|
||||
groupId: group.id
|
||||
});
|
||||
@@ -978,20 +1006,20 @@ export const scimServiceFactory = ({
|
||||
const allMembersUserIds = directMemberUserIds.concat(pendingGroupAdditionsUserIds);
|
||||
const allMembersUserIdsSet = new Set(allMembersUserIds);
|
||||
|
||||
const toAddUserIds = orgMemberships.filter((member) => !allMembersUserIdsSet.has(member.userId as string));
|
||||
const toAddUserIds = orgMemberships.filter((member) => !allMembersUserIdsSet.has(member.actorUserId as string));
|
||||
const toRemoveUserIds = allMembersUserIds.filter((userId) => !membersIdsSet.has(userId));
|
||||
|
||||
if (toAddUserIds.length) {
|
||||
await addUsersToGroupByUserIds({
|
||||
group,
|
||||
userIds: toAddUserIds.map((member) => member.userId as string),
|
||||
userIds: toAddUserIds.map((member) => member.actorUserId as string),
|
||||
userDAL,
|
||||
userGroupMembershipDAL,
|
||||
orgDAL,
|
||||
groupProjectDAL,
|
||||
projectKeyDAL,
|
||||
projectDAL,
|
||||
projectBotDAL,
|
||||
membershipGroupDAL,
|
||||
tx
|
||||
});
|
||||
}
|
||||
@@ -1002,7 +1030,7 @@ export const scimServiceFactory = ({
|
||||
userIds: toRemoveUserIds,
|
||||
userDAL,
|
||||
userGroupMembershipDAL,
|
||||
groupProjectDAL,
|
||||
membershipGroupDAL,
|
||||
projectKeyDAL,
|
||||
tx
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user