mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-10 14:28:35 +00:00
feat: completed membership identity
This commit is contained in:
@@ -2,7 +2,7 @@ import { MongoAbility } from "@casl/ability";
|
|||||||
import { MongoQuery } from "@ucast/mongo2js";
|
import { MongoQuery } from "@ucast/mongo2js";
|
||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { AccessScope, AccessScopeData, ActionProjectType, TMemberships } from "@app/db/schemas";
|
import { ActionProjectType, TMemberships } from "@app/db/schemas";
|
||||||
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
import { OrgPermissionSet } from "./org-permission";
|
import { OrgPermissionSet } from "./org-permission";
|
||||||
@@ -95,39 +95,40 @@ export type TPermissionServiceFactory = {
|
|||||||
}[];
|
}[];
|
||||||
}>;
|
}>;
|
||||||
// TODO(simp): switch to role dal later
|
// TODO(simp): switch to role dal later
|
||||||
getOrgPermissionByRole: (
|
getOrgPermissionByRoles: (
|
||||||
role: string,
|
roles: string[],
|
||||||
orgId: string
|
orgId: string
|
||||||
) => Promise<{
|
) => Promise<
|
||||||
permission: MongoAbility<OrgPermissionSet, MongoQuery>;
|
{
|
||||||
role?: {
|
permission: MongoAbility<OrgPermissionSet, MongoQuery>;
|
||||||
name: string;
|
role?: {
|
||||||
orgId: string;
|
name: string;
|
||||||
id: string;
|
id: string;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
slug: string;
|
slug: string;
|
||||||
permissions?: unknown;
|
permissions?: unknown;
|
||||||
description?: string | null | undefined;
|
description?: string | null | undefined;
|
||||||
};
|
};
|
||||||
}>;
|
}[]
|
||||||
getProjectPermissionByRole: (
|
>;
|
||||||
role: string,
|
getProjectPermissionByRoles: (
|
||||||
|
roles: string[],
|
||||||
projectId: string
|
projectId: string
|
||||||
) => Promise<{
|
) => Promise<
|
||||||
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
{
|
||||||
role?: {
|
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
||||||
name: string;
|
role?: {
|
||||||
version: number;
|
name: string;
|
||||||
id: string;
|
id: string;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
projectId: string;
|
slug: string;
|
||||||
slug: string;
|
permissions?: unknown;
|
||||||
permissions?: unknown;
|
description?: string | null | undefined;
|
||||||
description?: string | null | undefined;
|
};
|
||||||
};
|
}[]
|
||||||
}>;
|
>;
|
||||||
checkGroupProjectPermission: ({
|
checkGroupProjectPermission: ({
|
||||||
groupId,
|
groupId,
|
||||||
projectId,
|
projectId,
|
||||||
|
|||||||
@@ -25,9 +25,8 @@ import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/
|
|||||||
import { objectify } from "@app/lib/fn";
|
import { objectify } from "@app/lib/fn";
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
import { TIdentityDALFactory } from "@app/services/identity/identity-dal";
|
import { TIdentityDALFactory } from "@app/services/identity/identity-dal";
|
||||||
import { TOrgRoleDALFactory } from "@app/services/org/org-role-dal";
|
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { TProjectRoleDALFactory } from "@app/services/project-role/project-role-dal";
|
import { TRoleDALFactory } from "@app/services/role/role-dal";
|
||||||
import { TServiceTokenDALFactory } from "@app/services/service-token/service-token-dal";
|
import { TServiceTokenDALFactory } from "@app/services/service-token/service-token-dal";
|
||||||
import { TUserDALFactory } from "@app/services/user/user-dal";
|
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||||
|
|
||||||
@@ -99,25 +98,23 @@ const buildProjectPermissionRules = (projectUserRoles: TBuildProjectPermissionDT
|
|||||||
};
|
};
|
||||||
|
|
||||||
type TPermissionServiceFactoryDep = {
|
type TPermissionServiceFactoryDep = {
|
||||||
orgRoleDAL: Pick<TOrgRoleDALFactory, "findOne">;
|
|
||||||
projectRoleDAL: Pick<TProjectRoleDALFactory, "findOne">;
|
|
||||||
serviceTokenDAL: Pick<TServiceTokenDALFactory, "findById">;
|
serviceTokenDAL: Pick<TServiceTokenDALFactory, "findById">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findById">;
|
projectDAL: Pick<TProjectDALFactory, "findById">;
|
||||||
permissionDAL: TPermissionDALFactory;
|
permissionDAL: TPermissionDALFactory;
|
||||||
keyStore: TKeyStoreFactory;
|
keyStore: TKeyStoreFactory;
|
||||||
userDAL: Pick<TUserDALFactory, "findById">;
|
userDAL: Pick<TUserDALFactory, "findById">;
|
||||||
identityDAL: Pick<TIdentityDALFactory, "findById">;
|
identityDAL: Pick<TIdentityDALFactory, "findById">;
|
||||||
|
roleDAL: Pick<TRoleDALFactory, "find">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const permissionServiceFactory = ({
|
export const permissionServiceFactory = ({
|
||||||
permissionDAL,
|
permissionDAL,
|
||||||
orgRoleDAL,
|
|
||||||
projectRoleDAL,
|
|
||||||
serviceTokenDAL,
|
serviceTokenDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
userDAL,
|
userDAL,
|
||||||
identityDAL,
|
identityDAL,
|
||||||
keyStore
|
keyStore,
|
||||||
|
roleDAL
|
||||||
}: TPermissionServiceFactoryDep): TPermissionServiceFactory => {
|
}: TPermissionServiceFactoryDep): TPermissionServiceFactory => {
|
||||||
const invalidateProjectPermissionCache = async (projectId: string, tx?: Knex) => {
|
const invalidateProjectPermissionCache = async (projectId: string, tx?: Knex) => {
|
||||||
const projectPermissionDalVersionKey = KeyStorePrefixes.ProjectPermissionDalVersion(projectId);
|
const projectPermissionDalVersionKey = KeyStorePrefixes.ProjectPermissionDalVersion(projectId);
|
||||||
@@ -245,33 +242,6 @@ export const permissionServiceFactory = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// instead of actor type this will fetch by role slug. meaning it can be the pre defined slugs like
|
|
||||||
// admin member or user defined ones like biller etc
|
|
||||||
const getOrgPermissionByRole: TPermissionServiceFactory["getOrgPermissionByRole"] = async (role, orgId) => {
|
|
||||||
const isCustomRole = !Object.values(OrgMembershipRole).includes(role as OrgMembershipRole);
|
|
||||||
if (isCustomRole) {
|
|
||||||
const orgRole = await orgRoleDAL.findOne({ slug: role, orgId });
|
|
||||||
if (!orgRole)
|
|
||||||
throw new NotFoundError({
|
|
||||||
message: `Specified role '${role}' was not found in the organization with ID '${orgId}'`
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
permission: createMongoAbility<OrgPermissionSet>(
|
|
||||||
buildOrgPermissionRules([{ role: OrgMembershipRole.Custom, permissions: orgRole.permissions }]),
|
|
||||||
{
|
|
||||||
conditionsMatcher
|
|
||||||
}
|
|
||||||
),
|
|
||||||
role: orgRole
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
permission: createMongoAbility<OrgPermissionSet>(buildOrgPermissionRules([{ role, permissions: [] }]), {
|
|
||||||
conditionsMatcher
|
|
||||||
})
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const getServiceTokenProjectPermission = async ({
|
const getServiceTokenProjectPermission = async ({
|
||||||
serviceTokenId,
|
serviceTokenId,
|
||||||
projectId,
|
projectId,
|
||||||
@@ -572,30 +542,105 @@ export const permissionServiceFactory = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getProjectPermissionByRole: TPermissionServiceFactory["getProjectPermissionByRole"] = async (
|
// instead of actor type this will fetch by role slug. meaning it can be the pre defined slugs like
|
||||||
role,
|
// admin member or user defined ones like biller etc
|
||||||
projectId
|
const getOrgPermissionByRoles: TPermissionServiceFactory["getOrgPermissionByRoles"] = async (roles, orgId) => {
|
||||||
) => {
|
const formattedRoles = roles.map((role) => ({
|
||||||
const isCustomRole = !Object.values(ProjectMembershipRole).includes(role as ProjectMembershipRole);
|
name: role,
|
||||||
if (isCustomRole) {
|
isCustom: !Object.values(OrgMembershipRole).includes(role as OrgMembershipRole)
|
||||||
const projectRole = await projectRoleDAL.findOne({ slug: role, projectId });
|
}));
|
||||||
if (!projectRole) throw new NotFoundError({ message: `Specified role was not found: ${role}` });
|
|
||||||
const rules = buildProjectPermissionRules([
|
const customRoles = formattedRoles.filter((el) => el.isCustom).map((el) => el.name);
|
||||||
{ role: ProjectMembershipRole.Custom, permissions: projectRole.permissions }
|
const customRoleDetails = customRoles.length
|
||||||
]);
|
? await roleDAL.find({
|
||||||
return {
|
orgId,
|
||||||
permission: createMongoAbility<ProjectPermissionSet>(rules, {
|
$in: {
|
||||||
conditionsMatcher
|
slug: customRoles
|
||||||
}),
|
}
|
||||||
role: projectRole
|
})
|
||||||
};
|
: [];
|
||||||
|
if (customRoles.length !== customRoleDetails.length) {
|
||||||
|
const missingRoles = customRoles.filter((role) => !customRoleDetails.find((el) => el.slug === role));
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Specified roles '${missingRoles.join(",")}' was not found in the organization with ID '${orgId}'`
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const rules = buildProjectPermissionRules([{ role, permissions: [] }]);
|
return formattedRoles.map((el) => {
|
||||||
const permission = createMongoAbility<ProjectPermissionSet>(rules, {
|
if (el.isCustom) {
|
||||||
conditionsMatcher
|
const roleDetails = customRoleDetails.find((role) => role.slug === el.name);
|
||||||
|
return {
|
||||||
|
permission: createMongoAbility<OrgPermissionSet>(
|
||||||
|
buildOrgPermissionRules([{ role: OrgMembershipRole.Custom, permissions: roleDetails?.permissions || [] }]),
|
||||||
|
{
|
||||||
|
conditionsMatcher
|
||||||
|
}
|
||||||
|
),
|
||||||
|
role: roleDetails!
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
permission: createMongoAbility<OrgPermissionSet>(
|
||||||
|
buildOrgPermissionRules([{ role: el.name, permissions: [] }]),
|
||||||
|
{
|
||||||
|
conditionsMatcher
|
||||||
|
}
|
||||||
|
)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getProjectPermissionByRoles: TPermissionServiceFactory["getProjectPermissionByRoles"] = async (
|
||||||
|
roles,
|
||||||
|
projectId
|
||||||
|
) => {
|
||||||
|
const formattedRoles = roles.map((role) => ({
|
||||||
|
name: role,
|
||||||
|
isCustom: !Object.values(ProjectMembershipRole).includes(role as ProjectMembershipRole)
|
||||||
|
}));
|
||||||
|
|
||||||
|
const customRoles = formattedRoles.filter((el) => el.isCustom).map((el) => el.name);
|
||||||
|
const customRoleDetails = customRoles.length
|
||||||
|
? await roleDAL.find({
|
||||||
|
projectId,
|
||||||
|
$in: {
|
||||||
|
slug: customRoles
|
||||||
|
}
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
if (customRoles.length !== customRoleDetails.length) {
|
||||||
|
const missingRoles = customRoles.filter((role) => !customRoleDetails.find((el) => el.slug === role));
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Specified roles '${missingRoles.join(",")}' was not found in the project with ID '${projectId}'`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return formattedRoles.map((el) => {
|
||||||
|
if (el.isCustom) {
|
||||||
|
const roleDetails = customRoleDetails.find((role) => role.slug === el.name);
|
||||||
|
return {
|
||||||
|
permission: createMongoAbility<ProjectPermissionSet>(
|
||||||
|
buildProjectPermissionRules([
|
||||||
|
{ role: ProjectMembershipRole.Custom, permissions: roleDetails?.permissions || [] }
|
||||||
|
]),
|
||||||
|
{
|
||||||
|
conditionsMatcher
|
||||||
|
}
|
||||||
|
),
|
||||||
|
role: roleDetails!
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
permission: createMongoAbility<ProjectPermissionSet>(
|
||||||
|
buildProjectPermissionRules([{ role: el.name, permissions: [] }]),
|
||||||
|
{
|
||||||
|
conditionsMatcher
|
||||||
|
}
|
||||||
|
)
|
||||||
|
};
|
||||||
});
|
});
|
||||||
return { permission };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkGroupProjectPermission: TPermissionServiceFactory["checkGroupProjectPermission"] = async ({
|
const checkGroupProjectPermission: TPermissionServiceFactory["checkGroupProjectPermission"] = async ({
|
||||||
@@ -626,8 +671,8 @@ export const permissionServiceFactory = ({
|
|||||||
getOrgPermission,
|
getOrgPermission,
|
||||||
getProjectPermission,
|
getProjectPermission,
|
||||||
getProjectPermissions,
|
getProjectPermissions,
|
||||||
getOrgPermissionByRole,
|
getOrgPermissionByRoles,
|
||||||
getProjectPermissionByRole,
|
getProjectPermissionByRoles,
|
||||||
checkGroupProjectPermission,
|
checkGroupProjectPermission,
|
||||||
invalidateProjectPermissionCache
|
invalidateProjectPermissionCache
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,359 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { AccessScope, AccessScopeData, MembershipsSchema, TableName } from "@app/db/schemas";
|
||||||
|
import { BadRequestError, DatabaseError } from "@app/lib/errors";
|
||||||
|
import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
|
||||||
|
import { buildKnexFilterForSearchResource } from "@app/lib/search-resource/db";
|
||||||
|
import { TSearchResourceOperator } from "@app/lib/search-resource/search";
|
||||||
|
|
||||||
|
import { buildAuthMethods } from "../identity/identity-fns";
|
||||||
|
|
||||||
|
export type TMembershipIdentityDALFactory = ReturnType<typeof membershipIdentityDALFactory>;
|
||||||
|
|
||||||
|
type TFindIdentityArg = {
|
||||||
|
scopeData: AccessScopeData;
|
||||||
|
tx?: Knex;
|
||||||
|
filter: Partial<{
|
||||||
|
limit: number;
|
||||||
|
offset: number;
|
||||||
|
identityId: string;
|
||||||
|
name: Omit<TSearchResourceOperator, "number">;
|
||||||
|
role: Omit<TSearchResourceOperator, "number">;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type TGetIdentityByIdArg = {
|
||||||
|
scopeData: AccessScopeData;
|
||||||
|
tx?: Knex;
|
||||||
|
identityId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const membershipIdentityDALFactory = (db: TDbClient) => {
|
||||||
|
const orm = ormify(db, TableName.Membership);
|
||||||
|
|
||||||
|
const getIdentityById = async ({ scopeData, tx, identityId }: TGetIdentityByIdArg) => {
|
||||||
|
try {
|
||||||
|
const docs = await (tx || db.replicaNode())(TableName.Membership)
|
||||||
|
.whereNotNull(`${TableName.Membership}.actorIdentityId`)
|
||||||
|
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.Membership}.actorIdentityId`)
|
||||||
|
.join(TableName.MembershipRole, `${TableName.Membership}.id`, `${TableName.MembershipRole}.membershipId`)
|
||||||
|
.leftJoin(TableName.Role, `${TableName.MembershipRole}.customRoleId`, `${TableName.Role}.id`)
|
||||||
|
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
||||||
|
void queryBuilder
|
||||||
|
.on(`${TableName.Membership}.actorIdentityId`, `${TableName.IdentityMetadata}.userId`)
|
||||||
|
.andOn(`${TableName.Membership}.scopeOrgId`, `${TableName.IdentityMetadata}.orgId`);
|
||||||
|
})
|
||||||
|
.where(`${TableName.Membership}.scopeOrgId`, scopeData.orgId)
|
||||||
|
.where(`${TableName.Membership}.actorIdentityId`, identityId)
|
||||||
|
.where((qb) => {
|
||||||
|
if (scopeData.scope === AccessScope.Organization) {
|
||||||
|
void qb.where(`${TableName.Membership}.scope`, AccessScope.Organization);
|
||||||
|
} else if (scopeData.scope === AccessScope.Namespace) {
|
||||||
|
void qb
|
||||||
|
.where(`${TableName.Membership}.scope`, AccessScope.Namespace)
|
||||||
|
.where(`${TableName.Membership}.scopeNamespaceId`, scopeData.namespaceId)
|
||||||
|
.whereNull(`${TableName.Membership}.scopeNamespaceId`);
|
||||||
|
} else if (scopeData.scope === AccessScope.Project) {
|
||||||
|
void qb
|
||||||
|
.where(`${TableName.Membership}.scope`, AccessScope.Project)
|
||||||
|
.where(`${TableName.Membership}.scopeProjectId`, scopeData.projectId);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.leftJoin(
|
||||||
|
TableName.IdentityUniversalAuth,
|
||||||
|
`${TableName.Identity}.id`,
|
||||||
|
`${TableName.IdentityUniversalAuth}.identityId`
|
||||||
|
)
|
||||||
|
.leftJoin(TableName.IdentityGcpAuth, `${TableName.Identity}.id`, `${TableName.IdentityGcpAuth}.identityId`)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.IdentityAliCloudAuth,
|
||||||
|
`${TableName.Identity}.id`,
|
||||||
|
`${TableName.IdentityAliCloudAuth}.identityId`
|
||||||
|
)
|
||||||
|
.leftJoin(TableName.IdentityAwsAuth, `${TableName.Identity}.id`, `${TableName.IdentityAwsAuth}.identityId`)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.IdentityKubernetesAuth,
|
||||||
|
`${TableName.Identity}.id`,
|
||||||
|
`${TableName.IdentityKubernetesAuth}.identityId`
|
||||||
|
)
|
||||||
|
.leftJoin(TableName.IdentityOciAuth, `${TableName.Identity}.id`, `${TableName.IdentityOciAuth}.identityId`)
|
||||||
|
.leftJoin(TableName.IdentityOidcAuth, `${TableName.Identity}.id`, `${TableName.IdentityOidcAuth}.identityId`)
|
||||||
|
.leftJoin(TableName.IdentityAzureAuth, `${TableName.Identity}.id`, `${TableName.IdentityAzureAuth}.identityId`)
|
||||||
|
.leftJoin(TableName.IdentityTokenAuth, `${TableName.Identity}.id`, `${TableName.IdentityTokenAuth}.identityId`)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.IdentityTlsCertAuth,
|
||||||
|
`${TableName.Identity}.id`,
|
||||||
|
`${TableName.IdentityTlsCertAuth}.identityId`
|
||||||
|
)
|
||||||
|
.leftJoin(TableName.IdentityLdapAuth, `${TableName.Identity}.id`, `${TableName.IdentityLdapAuth}.identityId`)
|
||||||
|
.leftJoin(TableName.IdentityJwtAuth, `${TableName.Identity}.id`, `${TableName.IdentityJwtAuth}.identityId`)
|
||||||
|
.select(selectAllTableCols(TableName.Membership))
|
||||||
|
.select(
|
||||||
|
db.ref("name").withSchema(TableName.Identity).as("identityName"),
|
||||||
|
db.ref("id").withSchema(TableName.Identity).as("identityId"),
|
||||||
|
db.ref("hasDeleteProtection").withSchema(TableName.Identity).as("identityHasDeleteProtection"),
|
||||||
|
|
||||||
|
db.ref("slug").withSchema(TableName.Role).as("roleSlug"),
|
||||||
|
db.ref("id").withSchema(TableName.MembershipRole).as("membershipRoleId"),
|
||||||
|
db.ref("role").withSchema(TableName.MembershipRole).as("membershipRole"),
|
||||||
|
db.ref("temporaryMode").withSchema(TableName.MembershipRole).as("membershipRoleTemporaryMode"),
|
||||||
|
db.ref("isTemporary").withSchema(TableName.MembershipRole).as("membershipRoleIsTemporary"),
|
||||||
|
db.ref("temporaryRange").withSchema(TableName.MembershipRole).as("membershipRoleTemporaryRange"),
|
||||||
|
db
|
||||||
|
.ref("temporaryAccessStartTime")
|
||||||
|
.withSchema(TableName.MembershipRole)
|
||||||
|
.as("membershipRoleTemporaryAccessStartTime"),
|
||||||
|
db
|
||||||
|
.ref("temporaryAccessEndTime")
|
||||||
|
.withSchema(TableName.MembershipRole)
|
||||||
|
.as("membershipRoleTemporaryAccessEndTime"),
|
||||||
|
db.ref("createdAt").withSchema(TableName.MembershipRole).as("membershipRoleCreatedAt"),
|
||||||
|
db.ref("updatedAt").withSchema(TableName.MembershipRole).as("membershipRoleUpdatedAt"),
|
||||||
|
db.ref("id").withSchema(TableName.IdentityMetadata).as("metadataId"),
|
||||||
|
db.ref("key").withSchema(TableName.IdentityMetadata).as("metadataKey"),
|
||||||
|
db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue"),
|
||||||
|
db.ref("id").as("uaId").withSchema(TableName.IdentityUniversalAuth),
|
||||||
|
db.ref("id").as("gcpId").withSchema(TableName.IdentityGcpAuth),
|
||||||
|
db.ref("id").as("alicloudId").withSchema(TableName.IdentityAliCloudAuth),
|
||||||
|
db.ref("id").as("awsId").withSchema(TableName.IdentityAwsAuth),
|
||||||
|
db.ref("id").as("kubernetesId").withSchema(TableName.IdentityKubernetesAuth),
|
||||||
|
db.ref("id").as("ociId").withSchema(TableName.IdentityOciAuth),
|
||||||
|
db.ref("id").as("oidcId").withSchema(TableName.IdentityOidcAuth),
|
||||||
|
db.ref("id").as("azureId").withSchema(TableName.IdentityAzureAuth),
|
||||||
|
db.ref("id").as("tokenId").withSchema(TableName.IdentityTokenAuth),
|
||||||
|
db.ref("id").as("jwtId").withSchema(TableName.IdentityJwtAuth),
|
||||||
|
db.ref("id").as("ldapId").withSchema(TableName.IdentityLdapAuth),
|
||||||
|
db.ref("id").as("tlsCertId").withSchema(TableName.IdentityTlsCertAuth)
|
||||||
|
);
|
||||||
|
|
||||||
|
const data = sqlNestRelationships({
|
||||||
|
data: docs,
|
||||||
|
key: "id",
|
||||||
|
parentMapper: (el) => {
|
||||||
|
const {
|
||||||
|
identityId: actorIdentityId,
|
||||||
|
identityHasDeleteProtection,
|
||||||
|
identityName,
|
||||||
|
uaId,
|
||||||
|
awsId,
|
||||||
|
gcpId,
|
||||||
|
kubernetesId,
|
||||||
|
oidcId,
|
||||||
|
azureId,
|
||||||
|
alicloudId,
|
||||||
|
tokenId,
|
||||||
|
jwtId,
|
||||||
|
ociId,
|
||||||
|
ldapId,
|
||||||
|
tlsCertId
|
||||||
|
} = el;
|
||||||
|
return {
|
||||||
|
...MembershipsSchema.parse(el),
|
||||||
|
identity: {
|
||||||
|
name: identityName,
|
||||||
|
id: actorIdentityId,
|
||||||
|
hasDeleteProtection: identityHasDeleteProtection,
|
||||||
|
authMethods: buildAuthMethods({
|
||||||
|
uaId,
|
||||||
|
awsId,
|
||||||
|
gcpId,
|
||||||
|
kubernetesId,
|
||||||
|
oidcId,
|
||||||
|
azureId,
|
||||||
|
tokenId,
|
||||||
|
alicloudId,
|
||||||
|
jwtId,
|
||||||
|
ldapId,
|
||||||
|
ociId,
|
||||||
|
tlsCertId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
childrenMapper: [
|
||||||
|
{
|
||||||
|
key: "membershipRoleId",
|
||||||
|
label: "roles" as const,
|
||||||
|
mapper: ({
|
||||||
|
roleSlug,
|
||||||
|
membershipRoleId,
|
||||||
|
membershipRole,
|
||||||
|
membershipRoleIsTemporary,
|
||||||
|
membershipRoleTemporaryMode,
|
||||||
|
membershipRoleTemporaryRange,
|
||||||
|
membershipRoleTemporaryAccessEndTime,
|
||||||
|
membershipRoleTemporaryAccessStartTime,
|
||||||
|
membershipRoleCreatedAt,
|
||||||
|
membershipRoleUpdatedAt
|
||||||
|
}) => ({
|
||||||
|
id: membershipRoleId,
|
||||||
|
role: membershipRole,
|
||||||
|
customRoleSlug: roleSlug,
|
||||||
|
temporaryRange: membershipRoleTemporaryRange,
|
||||||
|
temporaryMode: membershipRoleTemporaryMode,
|
||||||
|
temporaryAccessStartTime: membershipRoleTemporaryAccessStartTime,
|
||||||
|
temporaryAccessEndTime: membershipRoleTemporaryAccessEndTime,
|
||||||
|
isTemporary: membershipRoleIsTemporary,
|
||||||
|
createdAt: membershipRoleCreatedAt,
|
||||||
|
updatedAt: membershipRoleUpdatedAt
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "metadataId",
|
||||||
|
label: "metadata" as const,
|
||||||
|
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
|
||||||
|
id: metadataId,
|
||||||
|
key: metadataKey,
|
||||||
|
value: metadataValue
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
return data?.[0];
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "MembershipGetByIdentityId" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const findIdentities = async ({ scopeData, tx, filter }: TFindIdentityArg) => {
|
||||||
|
try {
|
||||||
|
const paginatedIdentitys = (tx || db.replicaNode())(TableName.Membership)
|
||||||
|
.whereNotNull(`${TableName.Membership}.actorIdentityId`)
|
||||||
|
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.Membership}.actorIdentityId`)
|
||||||
|
.join(TableName.MembershipRole, `${TableName.Membership}.id`, `${TableName.MembershipRole}.membershipId`)
|
||||||
|
.leftJoin(TableName.Role, `${TableName.MembershipRole}.customRoleId`, `${TableName.Role}.id`)
|
||||||
|
.distinct(`${TableName.Membership}.id`)
|
||||||
|
.where(`${TableName.Membership}.scopeOrgId`, scopeData.orgId)
|
||||||
|
.where((qb) => {
|
||||||
|
if (filter.identityId) {
|
||||||
|
void qb.where(`${TableName.Identity}.id`, filter.identityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scopeData.scope === AccessScope.Organization) {
|
||||||
|
void qb.where(`${TableName.Membership}.scope`, AccessScope.Organization);
|
||||||
|
} else if (scopeData.scope === AccessScope.Namespace) {
|
||||||
|
void qb
|
||||||
|
.where(`${TableName.Membership}.scope`, AccessScope.Namespace)
|
||||||
|
.where(`${TableName.Membership}.scopeNamespaceId`, scopeData.namespaceId)
|
||||||
|
.whereNull(`${TableName.Membership}.scopeNamespaceId`);
|
||||||
|
} else if (scopeData.scope === AccessScope.Project) {
|
||||||
|
void qb
|
||||||
|
.where(`${TableName.Membership}.scope`, AccessScope.Project)
|
||||||
|
.where(`${TableName.Membership}.scopeProjectId`, scopeData.projectId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (filter.limit) void paginatedIdentitys.limit(filter.limit);
|
||||||
|
if (filter.offset) void paginatedIdentitys.offset(filter.offset);
|
||||||
|
|
||||||
|
if (filter.name || filter.role) {
|
||||||
|
buildKnexFilterForSearchResource(
|
||||||
|
paginatedIdentitys,
|
||||||
|
{
|
||||||
|
name: filter.name!,
|
||||||
|
role: filter.role!
|
||||||
|
},
|
||||||
|
(attr) => {
|
||||||
|
switch (attr) {
|
||||||
|
case "role":
|
||||||
|
return [`${TableName.Role}.slug`, `${TableName.MembershipRole}.role`];
|
||||||
|
case "name":
|
||||||
|
return `${TableName.Identity}.name`;
|
||||||
|
default:
|
||||||
|
throw new BadRequestError({ message: `Invalid ${String(attr)} provided` });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const docs = await (tx || db.replicaNode())(TableName.Membership)
|
||||||
|
.whereNotNull(`${TableName.Membership}.actorIdentityId`)
|
||||||
|
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.Membership}.actorIdentityId`)
|
||||||
|
.join(TableName.MembershipRole, `${TableName.Membership}.id`, `${TableName.MembershipRole}.membershipId`)
|
||||||
|
.leftJoin(TableName.Role, `${TableName.MembershipRole}.customRoleId`, `${TableName.Role}.id`)
|
||||||
|
.distinct(`${TableName.Membership}.id`)
|
||||||
|
.where(`${TableName.Membership}.scopeOrgId`, scopeData.orgId)
|
||||||
|
.whereIn(`${TableName.Membership}.id`, paginatedIdentitys)
|
||||||
|
.select(selectAllTableCols(TableName.Membership))
|
||||||
|
.select(
|
||||||
|
db.ref("name").withSchema(TableName.Identity).as("identityName"),
|
||||||
|
db.ref("id").withSchema(TableName.Identity).as("identityId"),
|
||||||
|
db.ref("hasDeleteProtection").withSchema(TableName.Identity).as("identityHasDeleteProtection"),
|
||||||
|
|
||||||
|
db.ref("slug").withSchema(TableName.Role).as("roleSlug"),
|
||||||
|
db.ref("id").withSchema(TableName.MembershipRole).as("membershipRoleId"),
|
||||||
|
db.ref("role").withSchema(TableName.MembershipRole).as("membershipRole"),
|
||||||
|
db.ref("temporaryMode").withSchema(TableName.MembershipRole).as("membershipRoleTemporaryMode"),
|
||||||
|
db.ref("isTemporary").withSchema(TableName.MembershipRole).as("membershipRoleIsTemporary"),
|
||||||
|
db.ref("temporaryRange").withSchema(TableName.MembershipRole).as("membershipRoleTemporaryRange"),
|
||||||
|
db
|
||||||
|
.ref("temporaryAccessStartTime")
|
||||||
|
.withSchema(TableName.MembershipRole)
|
||||||
|
.as("membershipRoleTemporaryAccessStartTime"),
|
||||||
|
db
|
||||||
|
.ref("temporaryAccessEndTime")
|
||||||
|
.withSchema(TableName.MembershipRole)
|
||||||
|
.as("membershipRoleTemporaryAccessEndTime"),
|
||||||
|
db.ref("createdAt").withSchema(TableName.MembershipRole).as("membershipRoleCreatedAt"),
|
||||||
|
db.ref("updatedAt").withSchema(TableName.MembershipRole).as("membershipRoleUpdatedAt")
|
||||||
|
)
|
||||||
|
.select(
|
||||||
|
db.raw(
|
||||||
|
`count(${TableName.Membership}."actorIdentityId") OVER(PARTITION BY ${TableName.Membership}."scopeOrgId") as total`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const data = sqlNestRelationships({
|
||||||
|
data: docs,
|
||||||
|
key: "id",
|
||||||
|
parentMapper: (el) => {
|
||||||
|
const { identityId: actorIdentityId, identityHasDeleteProtection, identityName } = el;
|
||||||
|
return {
|
||||||
|
...MembershipsSchema.parse(el),
|
||||||
|
identity: {
|
||||||
|
name: identityName,
|
||||||
|
id: actorIdentityId,
|
||||||
|
hasDeleteProtection: identityHasDeleteProtection
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
childrenMapper: [
|
||||||
|
{
|
||||||
|
key: "membershipRoleId",
|
||||||
|
label: "roles" as const,
|
||||||
|
mapper: ({
|
||||||
|
roleSlug,
|
||||||
|
membershipRoleId,
|
||||||
|
membershipRole,
|
||||||
|
membershipRoleIsTemporary,
|
||||||
|
membershipRoleTemporaryMode,
|
||||||
|
membershipRoleTemporaryRange,
|
||||||
|
membershipRoleTemporaryAccessEndTime,
|
||||||
|
membershipRoleTemporaryAccessStartTime,
|
||||||
|
membershipRoleCreatedAt,
|
||||||
|
membershipRoleUpdatedAt
|
||||||
|
}) => ({
|
||||||
|
id: membershipRoleId,
|
||||||
|
role: membershipRole,
|
||||||
|
customRoleSlug: roleSlug,
|
||||||
|
temporaryRange: membershipRoleTemporaryRange,
|
||||||
|
temporaryMode: membershipRoleTemporaryMode,
|
||||||
|
temporaryAccessStartTime: membershipRoleTemporaryAccessStartTime,
|
||||||
|
temporaryAccessEndTime: membershipRoleTemporaryAccessEndTime,
|
||||||
|
isTemporary: membershipRoleIsTemporary,
|
||||||
|
createdAt: membershipRoleCreatedAt,
|
||||||
|
updatedAt: membershipRoleUpdatedAt
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
return { data, totalCount: Number((data?.[0] as unknown as { total: number })?.total ?? 0) };
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "MembershipfindIdentity" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { ...orm, findIdentities, getIdentityById };
|
||||||
|
};
|
||||||
@@ -0,0 +1,325 @@
|
|||||||
|
import { AccessScope, ProjectMembershipRole, TemporaryPermissionMode, TMembershipRolesInsert } from "@app/db/schemas";
|
||||||
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||||
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { groupBy } from "@app/lib/fn";
|
||||||
|
import { ms } from "@app/lib/ms";
|
||||||
|
import { SearchResourceOperators } from "@app/lib/search-resource/search";
|
||||||
|
|
||||||
|
import { TMembershipRoleDALFactory } from "../membership/membership-role-dal";
|
||||||
|
import { TOrgDALFactory } from "../org/org-dal";
|
||||||
|
import { TRoleDALFactory } from "../role/role-dal";
|
||||||
|
import { TMembershipIdentityDALFactory } from "./membership-identity-dal";
|
||||||
|
import {
|
||||||
|
TCreateMembershipIdentityDTO,
|
||||||
|
TDeleteMembershipIdentityDTO,
|
||||||
|
TGetMembershipIdentityByIdentityIdDTO,
|
||||||
|
TListMembershipIdentityDTO,
|
||||||
|
TUpdateMembershipIdentityDTO
|
||||||
|
} from "./membership-identity-types";
|
||||||
|
import { newNamespaceMembershipIdentityFactory } from "./namespace/namespace-membership-identity-factory";
|
||||||
|
import { newOrgMembershipIdentityFactory } from "./org/org-membership-identity-factory";
|
||||||
|
import { newProjectMembershipIdentityFactory } from "./project/project-membership-identity-factory";
|
||||||
|
|
||||||
|
type TMembershipIdentityServiceFactoryDep = {
|
||||||
|
membershipIdentityDAL: TMembershipIdentityDALFactory;
|
||||||
|
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "insertMany" | "delete">;
|
||||||
|
roleDAL: Pick<TRoleDALFactory, "find">;
|
||||||
|
permissionService: Pick<
|
||||||
|
TPermissionServiceFactory,
|
||||||
|
"getOrgPermission" | "getProjectPermission" | "getProjectPermissionByRoles" | "getOrgPermissionByRoles"
|
||||||
|
>;
|
||||||
|
orgDAL: Pick<TOrgDALFactory, "findById">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TMembershipIdentityServiceFactory = ReturnType<typeof membershipIdentityServiceFactory>;
|
||||||
|
|
||||||
|
export const membershipIdentityServiceFactory = ({
|
||||||
|
membershipIdentityDAL,
|
||||||
|
roleDAL,
|
||||||
|
membershipRoleDAL,
|
||||||
|
permissionService,
|
||||||
|
orgDAL
|
||||||
|
}: TMembershipIdentityServiceFactoryDep) => {
|
||||||
|
const scopeFactory = {
|
||||||
|
[AccessScope.Organization]: newOrgMembershipIdentityFactory({
|
||||||
|
orgDAL,
|
||||||
|
permissionService
|
||||||
|
}),
|
||||||
|
[AccessScope.Project]: newProjectMembershipIdentityFactory({
|
||||||
|
membershipIdentityDAL,
|
||||||
|
orgDAL,
|
||||||
|
permissionService
|
||||||
|
}),
|
||||||
|
[AccessScope.Namespace]: newNamespaceMembershipIdentityFactory({})
|
||||||
|
};
|
||||||
|
|
||||||
|
const createMembership = async (dto: TCreateMembershipIdentityDTO) => {
|
||||||
|
const { scopeData, data } = dto;
|
||||||
|
const factory = scopeFactory[scopeData.scope];
|
||||||
|
|
||||||
|
const hasOnePermanentRole = data.roles.some((el) => el.isTemporary);
|
||||||
|
if (hasOnePermanentRole) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Identity must have atleast one permanent role"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const isInvalidTemporaryRole = data.roles.some((el) => {
|
||||||
|
if (el.isTemporary) {
|
||||||
|
if (!el.temporaryAccessStartTime || !el.temporaryRange) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
if (isInvalidTemporaryRole) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Temporary role must have access start time and range"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const scopeDatabaseFields = factory.getScopeDatabaseFields(dto.scopeData);
|
||||||
|
await factory.onCreateMembershipIdentityGuard(dto);
|
||||||
|
|
||||||
|
const customInputRoles = data.roles.filter((el) => factory.isCustomRole(el.role));
|
||||||
|
const hasCustomRole = customInputRoles.length > 0;
|
||||||
|
|
||||||
|
const scopeField = factory.getScopeField(dto.scopeData);
|
||||||
|
const customRoles = hasCustomRole
|
||||||
|
? await roleDAL.find({
|
||||||
|
[scopeField.key]: scopeField.value,
|
||||||
|
$in: { slug: customInputRoles.map(({ role }) => role) }
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
if (customRoles.length !== customInputRoles.length) {
|
||||||
|
throw new NotFoundError({ message: "One or more custom roles not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const customRolesGroupBySlug = groupBy(customRoles, ({ slug }) => slug);
|
||||||
|
|
||||||
|
const membership = await membershipIdentityDAL.transaction(async (tx) => {
|
||||||
|
const doc = await membershipIdentityDAL.create(
|
||||||
|
{
|
||||||
|
scope: scopeData.scope,
|
||||||
|
...scopeDatabaseFields,
|
||||||
|
actorIdentityId: dto.permission.id
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
const roleDocs: TMembershipRolesInsert[] = [];
|
||||||
|
data.roles.forEach((membershipRole) => {
|
||||||
|
const isCustomRole = Boolean(customRolesGroupBySlug?.[membershipRole.role]?.[0]);
|
||||||
|
if (membershipRole.isTemporary) {
|
||||||
|
const relativeTimeInMs = membershipRole.temporaryRange ? ms(membershipRole.temporaryRange) : null;
|
||||||
|
roleDocs.push({
|
||||||
|
membershipId: doc.id,
|
||||||
|
role: isCustomRole ? ProjectMembershipRole.Custom : membershipRole.role,
|
||||||
|
customRoleId: customRolesGroupBySlug[membershipRole.role]
|
||||||
|
? customRolesGroupBySlug[membershipRole.role][0].id
|
||||||
|
: null,
|
||||||
|
isTemporary: true,
|
||||||
|
temporaryMode: TemporaryPermissionMode.Relative,
|
||||||
|
temporaryRange: membershipRole.temporaryRange,
|
||||||
|
temporaryAccessStartTime: new Date(membershipRole.temporaryAccessStartTime as string),
|
||||||
|
temporaryAccessEndTime: new Date(
|
||||||
|
new Date(membershipRole.temporaryAccessStartTime as string).getTime() + (relativeTimeInMs as number)
|
||||||
|
)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
roleDocs.push({
|
||||||
|
membershipId: doc.id,
|
||||||
|
role: isCustomRole ? ProjectMembershipRole.Custom : membershipRole.role,
|
||||||
|
customRoleId: customRolesGroupBySlug[membershipRole.role]
|
||||||
|
? customRolesGroupBySlug[membershipRole.role][0].id
|
||||||
|
: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await membershipRoleDAL.insertMany(roleDocs, tx);
|
||||||
|
return doc;
|
||||||
|
});
|
||||||
|
|
||||||
|
return { membership };
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateMembership = async (dto: TUpdateMembershipIdentityDTO) => {
|
||||||
|
const { scopeData, data } = dto;
|
||||||
|
const factory = scopeFactory[scopeData.scope];
|
||||||
|
|
||||||
|
await factory.onUpdateMembershipIdentityGuard(dto);
|
||||||
|
|
||||||
|
const customInputRoles = data.roles.filter((el) => factory.isCustomRole(el.role));
|
||||||
|
const hasCustomRole = customInputRoles.length > 0;
|
||||||
|
|
||||||
|
const hasOnePermanentRole = data.roles.some((el) => el.isTemporary);
|
||||||
|
if (hasOnePermanentRole) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Identity must have atleast one permanent role"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const isInvalidTemporaryRole = data.roles.some((el) => {
|
||||||
|
if (el.isTemporary) {
|
||||||
|
if (!el.temporaryAccessStartTime || !el.temporaryRange) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
if (isInvalidTemporaryRole) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Temporary role must have access start time and range"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const scopeDatabaseFields = factory.getScopeDatabaseFields(dto.scopeData);
|
||||||
|
const existingMembership = await membershipIdentityDAL.findOne({
|
||||||
|
scope: scopeData.scope,
|
||||||
|
...scopeDatabaseFields,
|
||||||
|
actorIdentityId: dto.selector.identityId
|
||||||
|
});
|
||||||
|
if (!existingMembership)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Identity doesn't have membership"
|
||||||
|
});
|
||||||
|
|
||||||
|
const scopeField = factory.getScopeField(dto.scopeData);
|
||||||
|
const customRoles = hasCustomRole
|
||||||
|
? await roleDAL.find({
|
||||||
|
[scopeField.key]: scopeField.value,
|
||||||
|
$in: { slug: customInputRoles.map(({ role }) => role) }
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
if (customRoles.length !== customInputRoles.length) {
|
||||||
|
throw new NotFoundError({ message: "One or more custom roles not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const customRolesGroupBySlug = groupBy(customRoles, ({ slug }) => slug);
|
||||||
|
|
||||||
|
const membershipDoc = await membershipIdentityDAL.transaction(async (tx) => {
|
||||||
|
const doc = await membershipIdentityDAL.updateById(
|
||||||
|
existingMembership.id,
|
||||||
|
{
|
||||||
|
isActive: data.isActive
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
const roleDocs: TMembershipRolesInsert[] = [];
|
||||||
|
data.roles.forEach((membershipRole) => {
|
||||||
|
const isCustomRole = Boolean(customRolesGroupBySlug?.[membershipRole.role]?.[0]);
|
||||||
|
if (membershipRole.isTemporary) {
|
||||||
|
const relativeTimeInMs = membershipRole.temporaryRange ? ms(membershipRole.temporaryRange) : null;
|
||||||
|
roleDocs.push({
|
||||||
|
membershipId: doc.id,
|
||||||
|
role: isCustomRole ? ProjectMembershipRole.Custom : membershipRole.role,
|
||||||
|
customRoleId: customRolesGroupBySlug[membershipRole.role]
|
||||||
|
? customRolesGroupBySlug[membershipRole.role][0].id
|
||||||
|
: null,
|
||||||
|
isTemporary: true,
|
||||||
|
temporaryMode: TemporaryPermissionMode.Relative,
|
||||||
|
temporaryRange: membershipRole.temporaryRange,
|
||||||
|
temporaryAccessStartTime: new Date(membershipRole.temporaryAccessStartTime as string),
|
||||||
|
temporaryAccessEndTime: new Date(
|
||||||
|
new Date(membershipRole.temporaryAccessStartTime as string).getTime() + (relativeTimeInMs as number)
|
||||||
|
)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
roleDocs.push({
|
||||||
|
membershipId: doc.id,
|
||||||
|
role: isCustomRole ? ProjectMembershipRole.Custom : membershipRole.role,
|
||||||
|
customRoleId: customRolesGroupBySlug[membershipRole.role]
|
||||||
|
? customRolesGroupBySlug[membershipRole.role][0].id
|
||||||
|
: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await membershipRoleDAL.delete(
|
||||||
|
{
|
||||||
|
membershipId: doc.id
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
await membershipRoleDAL.insertMany(roleDocs, tx);
|
||||||
|
return doc;
|
||||||
|
});
|
||||||
|
|
||||||
|
return { membership: membershipDoc };
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteMembership = async (dto: TDeleteMembershipIdentityDTO) => {
|
||||||
|
const { scopeData } = dto;
|
||||||
|
const factory = scopeFactory[scopeData.scope];
|
||||||
|
|
||||||
|
await factory.onDeleteMembershipIdentityGuard(dto);
|
||||||
|
|
||||||
|
const scopeDatabaseFields = factory.getScopeDatabaseFields(dto.scopeData);
|
||||||
|
const existingMembership = await membershipIdentityDAL.findOne({
|
||||||
|
scope: scopeData.scope,
|
||||||
|
...scopeDatabaseFields,
|
||||||
|
actorIdentityId: dto.selector.identityId
|
||||||
|
});
|
||||||
|
if (!existingMembership)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Identity doesn't have membership"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingMembership.actorIdentityId === dto.permission.id)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "You can't delete you own membership"
|
||||||
|
});
|
||||||
|
|
||||||
|
const membershipDoc = await membershipIdentityDAL.transaction(async (tx) => {
|
||||||
|
await membershipRoleDAL.delete({ membershipId: existingMembership.id }, tx);
|
||||||
|
const doc = await membershipIdentityDAL.deleteById(existingMembership.id, tx);
|
||||||
|
return doc;
|
||||||
|
});
|
||||||
|
return { membership: membershipDoc };
|
||||||
|
};
|
||||||
|
|
||||||
|
const listMemberships = async (dto: TListMembershipIdentityDTO) => {
|
||||||
|
const { scopeData } = dto;
|
||||||
|
const factory = scopeFactory[scopeData.scope];
|
||||||
|
|
||||||
|
await factory.onListMembershipIdentityGuard(dto);
|
||||||
|
const memberships = await membershipIdentityDAL.findIdentities({
|
||||||
|
scopeData,
|
||||||
|
filter: {
|
||||||
|
limit: dto.data.limit,
|
||||||
|
offset: dto.data.offset,
|
||||||
|
name: dto.data.identityName
|
||||||
|
? {
|
||||||
|
[SearchResourceOperators.$contains]: dto.data.identityName
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
role: dto.data.roles.length
|
||||||
|
? {
|
||||||
|
[SearchResourceOperators.$in]: dto.data.roles
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return memberships;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMembershipByIdentityId = async (dto: TGetMembershipIdentityByIdentityIdDTO) => {
|
||||||
|
const { scopeData, selector } = dto;
|
||||||
|
const factory = scopeFactory[scopeData.scope];
|
||||||
|
|
||||||
|
await factory.onGetMembershipIdentityByIdentityIdGuard(dto);
|
||||||
|
const membership = await membershipIdentityDAL.getIdentityById({
|
||||||
|
scopeData,
|
||||||
|
identityId: selector.identityId
|
||||||
|
});
|
||||||
|
if (!membership) throw new NotFoundError({ message: `Identity membership not found` });
|
||||||
|
|
||||||
|
return membership;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
createMembership,
|
||||||
|
updateMembership,
|
||||||
|
deleteMembership,
|
||||||
|
listMemberships,
|
||||||
|
getMembershipByIdentityId
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import { AccessScopeData, TemporaryPermissionMode } from "@app/db/schemas";
|
||||||
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
|
|
||||||
|
export interface TMembershipIdentityScopeFactory {
|
||||||
|
onCreateMembershipIdentityGuard: (arg: TCreateMembershipIdentityDTO) => Promise<void>;
|
||||||
|
|
||||||
|
onUpdateMembershipIdentityGuard: (arg: TUpdateMembershipIdentityDTO) => Promise<void>;
|
||||||
|
onDeleteMembershipIdentityGuard: (arg: TDeleteMembershipIdentityDTO) => Promise<void>;
|
||||||
|
onListMembershipIdentityGuard: (arg: TListMembershipIdentityDTO) => Promise<void>;
|
||||||
|
onGetMembershipIdentityByIdentityIdGuard: (arg: TGetMembershipIdentityByIdentityIdDTO) => Promise<void>;
|
||||||
|
getScopeField: (scope: AccessScopeData) => { key: "orgId" | "namespaceId" | "projectId"; value: string };
|
||||||
|
getScopeDatabaseFields: (scope: AccessScopeData) => {
|
||||||
|
scopeOrgId: string;
|
||||||
|
scopeNamespaceId?: string | null;
|
||||||
|
scopeProjectId?: string | null;
|
||||||
|
};
|
||||||
|
isCustomRole: (role: string) => boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TCreateMembershipIdentityDTO = {
|
||||||
|
permission: OrgServiceActor;
|
||||||
|
scopeData: AccessScopeData;
|
||||||
|
data: {
|
||||||
|
identityId: string;
|
||||||
|
roles: {
|
||||||
|
role: string;
|
||||||
|
isTemporary: boolean;
|
||||||
|
temporaryMode?: TemporaryPermissionMode.Relative;
|
||||||
|
temporaryRange?: string;
|
||||||
|
temporaryAccessStartTime?: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TUpdateMembershipIdentityDTO = {
|
||||||
|
permission: OrgServiceActor;
|
||||||
|
scopeData: AccessScopeData;
|
||||||
|
selector: {
|
||||||
|
identityId: string;
|
||||||
|
};
|
||||||
|
data: {
|
||||||
|
isActive?: boolean;
|
||||||
|
metadata?: { key: string; value: string }[];
|
||||||
|
roles: {
|
||||||
|
role: string;
|
||||||
|
isTemporary: boolean;
|
||||||
|
temporaryMode?: TemporaryPermissionMode.Relative;
|
||||||
|
temporaryRange?: string;
|
||||||
|
temporaryAccessStartTime?: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TListMembershipIdentityDTO = {
|
||||||
|
permission: OrgServiceActor;
|
||||||
|
scopeData: AccessScopeData;
|
||||||
|
selector: {
|
||||||
|
identityId: string;
|
||||||
|
};
|
||||||
|
data: {
|
||||||
|
limit?: number;
|
||||||
|
offset?: number;
|
||||||
|
identityName?: string;
|
||||||
|
roles: string[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TDeleteMembershipIdentityDTO = {
|
||||||
|
permission: OrgServiceActor;
|
||||||
|
scopeData: AccessScopeData;
|
||||||
|
selector: {
|
||||||
|
identityId: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TGetMembershipIdentityByIdentityIdDTO = {
|
||||||
|
permission: OrgServiceActor;
|
||||||
|
scopeData: AccessScopeData;
|
||||||
|
selector: {
|
||||||
|
identityId: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
+64
@@ -0,0 +1,64 @@
|
|||||||
|
import { AccessScope } from "@app/db/schemas";
|
||||||
|
import { InternalServerError } from "@app/lib/errors";
|
||||||
|
|
||||||
|
import { TMembershipIdentityScopeFactory } from "../membership-identity-types";
|
||||||
|
|
||||||
|
type TNamespaceMembershipIdentityScopeFactoryDep = Record<string, never>;
|
||||||
|
|
||||||
|
export const newNamespaceMembershipIdentityFactory = (
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
deps: TNamespaceMembershipIdentityScopeFactoryDep
|
||||||
|
): TMembershipIdentityScopeFactory => {
|
||||||
|
const getScopeField: TMembershipIdentityScopeFactory["getScopeField"] = (dto) => {
|
||||||
|
if (dto.scope === AccessScope.Namespace) {
|
||||||
|
return { key: "namespaceId" as const, value: dto.namespaceId };
|
||||||
|
}
|
||||||
|
throw new InternalServerError({ message: "Invalid scope provided for the namespace factory" });
|
||||||
|
};
|
||||||
|
|
||||||
|
const getScopeDatabaseFields: TMembershipIdentityScopeFactory["getScopeDatabaseFields"] = (dto) => {
|
||||||
|
if (dto.scope === AccessScope.Namespace) {
|
||||||
|
return { scopeOrgId: dto.orgId, scopeNamespaceId: dto.namespaceId };
|
||||||
|
}
|
||||||
|
throw new InternalServerError({ message: "Invalid scope provided for the namespace factory" });
|
||||||
|
};
|
||||||
|
|
||||||
|
const isCustomRole: TMembershipIdentityScopeFactory["isCustomRole"] = () => {
|
||||||
|
throw new InternalServerError({ message: "Namespace membership user isCustomRole not implemented" });
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCreateMembershipIdentityGuard: TMembershipIdentityScopeFactory["onCreateMembershipIdentityGuard"] =
|
||||||
|
async () => {
|
||||||
|
throw new InternalServerError({ message: "Namespace membership user create not implemented" });
|
||||||
|
};
|
||||||
|
|
||||||
|
const onUpdateMembershipIdentityGuard: TMembershipIdentityScopeFactory["onUpdateMembershipIdentityGuard"] =
|
||||||
|
async () => {
|
||||||
|
throw new InternalServerError({ message: "Namespace membership user update not implemented" });
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDeleteMembershipIdentityGuard: TMembershipIdentityScopeFactory["onDeleteMembershipIdentityGuard"] =
|
||||||
|
async () => {
|
||||||
|
throw new InternalServerError({ message: "Namespace membership user delete not implemented" });
|
||||||
|
};
|
||||||
|
|
||||||
|
const onListMembershipIdentityGuard: TMembershipIdentityScopeFactory["onListMembershipIdentityGuard"] = async () => {
|
||||||
|
throw new InternalServerError({ message: "Namespace membership user list not implemented" });
|
||||||
|
};
|
||||||
|
|
||||||
|
const onGetMembershipIdentityByIdentityIdGuard: TMembershipIdentityScopeFactory["onGetMembershipIdentityByIdentityIdGuard"] =
|
||||||
|
async () => {
|
||||||
|
throw new InternalServerError({ message: "Namespace membership user get by user id not implemented" });
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
onCreateMembershipIdentityGuard,
|
||||||
|
onUpdateMembershipIdentityGuard,
|
||||||
|
onDeleteMembershipIdentityGuard,
|
||||||
|
onListMembershipIdentityGuard,
|
||||||
|
onGetMembershipIdentityByIdentityIdGuard,
|
||||||
|
getScopeField,
|
||||||
|
getScopeDatabaseFields,
|
||||||
|
isCustomRole
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
|
import { AccessScope, OrgMembershipRole } from "@app/db/schemas";
|
||||||
|
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||||
|
import {
|
||||||
|
constructPermissionErrorMessage,
|
||||||
|
validatePrivilegeChangeOperation
|
||||||
|
} from "@app/ee/services/permission/permission-fns";
|
||||||
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||||
|
import { BadRequestError, InternalServerError, PermissionBoundaryError } from "@app/lib/errors";
|
||||||
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
|
import { isCustomOrgRole } from "@app/services/org/org-role-fns";
|
||||||
|
|
||||||
|
import { TMembershipIdentityScopeFactory } from "../membership-identity-types";
|
||||||
|
|
||||||
|
type TOrgMembershipIdentityScopeFactoryDep = {
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getOrgPermissionByRoles">;
|
||||||
|
orgDAL: Pick<TOrgDALFactory, "findById">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const newOrgMembershipIdentityFactory = ({
|
||||||
|
permissionService,
|
||||||
|
orgDAL
|
||||||
|
}: TOrgMembershipIdentityScopeFactoryDep): TMembershipIdentityScopeFactory => {
|
||||||
|
const getScopeField: TMembershipIdentityScopeFactory["getScopeField"] = (dto) => {
|
||||||
|
if (dto.scope === AccessScope.Organization) {
|
||||||
|
return { key: "orgId" as const, value: dto.orgId };
|
||||||
|
}
|
||||||
|
throw new InternalServerError({ message: "Invalid scope provided for the org factory" });
|
||||||
|
};
|
||||||
|
|
||||||
|
const getScopeDatabaseFields: TMembershipIdentityScopeFactory["getScopeDatabaseFields"] = (dto) => {
|
||||||
|
if (dto.scope === AccessScope.Organization) {
|
||||||
|
return { scopeOrgId: dto.orgId };
|
||||||
|
}
|
||||||
|
throw new InternalServerError({ message: "Invalid scope provided for the org factory" });
|
||||||
|
};
|
||||||
|
|
||||||
|
const isCustomRole: TMembershipIdentityScopeFactory["isCustomRole"] = (role: string) => isCustomOrgRole(role);
|
||||||
|
|
||||||
|
const onCreateMembershipIdentityGuard: TMembershipIdentityScopeFactory["onCreateMembershipIdentityGuard"] =
|
||||||
|
async () => {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Organizatin membership cannot be created for organization scoped identity"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onUpdateMembershipIdentityGuard: TMembershipIdentityScopeFactory["onUpdateMembershipIdentityGuard"] = async (
|
||||||
|
dto
|
||||||
|
) => {
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
dto.permission.type,
|
||||||
|
dto.permission.id,
|
||||||
|
dto.permission.orgId,
|
||||||
|
dto.permission.authMethod,
|
||||||
|
dto.permission.orgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||||
|
const permissionRoles = await permissionService.getOrgPermissionByRoles(
|
||||||
|
dto.data.roles.map((el) => el.role),
|
||||||
|
dto.permission.orgId
|
||||||
|
);
|
||||||
|
|
||||||
|
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(dto.permission.orgId);
|
||||||
|
for (const permissionRole of permissionRoles) {
|
||||||
|
if (permissionRole?.role?.name !== OrgMembershipRole.NoAccess) {
|
||||||
|
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||||
|
shouldUseNewPrivilegeSystem,
|
||||||
|
OrgPermissionIdentityActions.GrantPrivileges,
|
||||||
|
OrgPermissionSubjects.Identity,
|
||||||
|
permission,
|
||||||
|
permissionRole.permission
|
||||||
|
);
|
||||||
|
if (!permissionBoundary.isValid)
|
||||||
|
throw new PermissionBoundaryError({
|
||||||
|
message: constructPermissionErrorMessage(
|
||||||
|
"Failed to create identity org membership",
|
||||||
|
shouldUseNewPrivilegeSystem,
|
||||||
|
OrgPermissionIdentityActions.GrantPrivileges,
|
||||||
|
OrgPermissionSubjects.Identity
|
||||||
|
),
|
||||||
|
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDeleteMembershipIdentityGuard: TMembershipIdentityScopeFactory["onDeleteMembershipIdentityGuard"] =
|
||||||
|
async () => {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Organizatin membership cannot be created for organization scoped identity"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onListMembershipIdentityGuard: TMembershipIdentityScopeFactory["onListMembershipIdentityGuard"] = async (
|
||||||
|
dto
|
||||||
|
) => {
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
dto.permission.type,
|
||||||
|
dto.permission.id,
|
||||||
|
dto.permission.orgId,
|
||||||
|
dto.permission.authMethod,
|
||||||
|
dto.permission.orgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onGetMembershipIdentityByIdentityIdGuard: TMembershipIdentityScopeFactory["onGetMembershipIdentityByIdentityIdGuard"] =
|
||||||
|
async (dto) => {
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
dto.permission.type,
|
||||||
|
dto.permission.id,
|
||||||
|
dto.permission.orgId,
|
||||||
|
dto.permission.authMethod,
|
||||||
|
dto.permission.orgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
onCreateMembershipIdentityGuard,
|
||||||
|
onUpdateMembershipIdentityGuard,
|
||||||
|
onDeleteMembershipIdentityGuard,
|
||||||
|
onListMembershipIdentityGuard,
|
||||||
|
onGetMembershipIdentityByIdentityIdGuard,
|
||||||
|
getScopeField,
|
||||||
|
getScopeDatabaseFields,
|
||||||
|
isCustomRole
|
||||||
|
};
|
||||||
|
};
|
||||||
+210
@@ -0,0 +1,210 @@
|
|||||||
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
|
import { AccessScope, ActionProjectType, ProjectMembershipRole } from "@app/db/schemas";
|
||||||
|
import {
|
||||||
|
constructPermissionErrorMessage,
|
||||||
|
validatePrivilegeChangeOperation
|
||||||
|
} from "@app/ee/services/permission/permission-fns";
|
||||||
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||||
|
import {
|
||||||
|
isCustomProjectRole,
|
||||||
|
ProjectPermissionIdentityActions,
|
||||||
|
ProjectPermissionSub
|
||||||
|
} from "@app/ee/services/permission/project-permission";
|
||||||
|
import { BadRequestError, InternalServerError, PermissionBoundaryError } from "@app/lib/errors";
|
||||||
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
|
|
||||||
|
import { TMembershipIdentityDALFactory } from "../membership-identity-dal";
|
||||||
|
import { TMembershipIdentityScopeFactory } from "../membership-identity-types";
|
||||||
|
|
||||||
|
type TProjectMembershipIdentityScopeFactoryDep = {
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getProjectPermissionByRoles">;
|
||||||
|
orgDAL: Pick<TOrgDALFactory, "findById">;
|
||||||
|
membershipIdentityDAL: Pick<TMembershipIdentityDALFactory, "findOne">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const newProjectMembershipIdentityFactory = ({
|
||||||
|
permissionService,
|
||||||
|
orgDAL,
|
||||||
|
membershipIdentityDAL
|
||||||
|
}: TProjectMembershipIdentityScopeFactoryDep): TMembershipIdentityScopeFactory => {
|
||||||
|
const getScopeField: TMembershipIdentityScopeFactory["getScopeField"] = (dto) => {
|
||||||
|
if (dto.scope === AccessScope.Project) {
|
||||||
|
return { key: "projectId" as const, value: dto.projectId };
|
||||||
|
}
|
||||||
|
throw new InternalServerError({ message: "Invalid scope provided for the project factory" });
|
||||||
|
};
|
||||||
|
|
||||||
|
const getScopeDatabaseFields: TMembershipIdentityScopeFactory["getScopeDatabaseFields"] = (dto) => {
|
||||||
|
if (dto.scope === AccessScope.Project) {
|
||||||
|
return { scopeOrgId: dto.orgId, scopeProjectId: dto.projectId };
|
||||||
|
}
|
||||||
|
throw new InternalServerError({ message: "Invalid scope provided for the project factory" });
|
||||||
|
};
|
||||||
|
|
||||||
|
const isCustomRole: TMembershipIdentityScopeFactory["isCustomRole"] = (role) => isCustomProjectRole(role);
|
||||||
|
|
||||||
|
const onCreateMembershipIdentityGuard: TMembershipIdentityScopeFactory["onCreateMembershipIdentityGuard"] = async (
|
||||||
|
dto
|
||||||
|
) => {
|
||||||
|
const scope = getScopeField(dto.scopeData);
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor: dto.permission.type,
|
||||||
|
actorId: dto.permission.id,
|
||||||
|
actionProjectType: ActionProjectType.Any,
|
||||||
|
actorAuthMethod: dto.permission.authMethod,
|
||||||
|
projectId: scope.value,
|
||||||
|
actorOrgId: dto.permission.orgId
|
||||||
|
});
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionIdentityActions.Create,
|
||||||
|
ProjectPermissionSub.Identity
|
||||||
|
);
|
||||||
|
const orgMembership = await membershipIdentityDAL.findOne({
|
||||||
|
actorIdentityId: dto.data.identityId,
|
||||||
|
scopeOrgId: dto.permission.orgId,
|
||||||
|
scope: AccessScope.Organization
|
||||||
|
});
|
||||||
|
if (!orgMembership)
|
||||||
|
throw new BadRequestError({ message: `Identity ${dto.data.identityId} is missing organization membership` });
|
||||||
|
|
||||||
|
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(dto.permission.orgId);
|
||||||
|
const permissionRoles = await permissionService.getProjectPermissionByRoles(
|
||||||
|
dto.data.roles.map((el) => el.role),
|
||||||
|
scope.value
|
||||||
|
);
|
||||||
|
for (const permissionRole of permissionRoles) {
|
||||||
|
if (permissionRole?.role?.name !== ProjectMembershipRole.NoAccess) {
|
||||||
|
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||||
|
shouldUseNewPrivilegeSystem,
|
||||||
|
ProjectPermissionIdentityActions.GrantPrivileges,
|
||||||
|
ProjectPermissionSub.Identity,
|
||||||
|
permission,
|
||||||
|
permissionRole.permission
|
||||||
|
);
|
||||||
|
if (!permissionBoundary.isValid)
|
||||||
|
throw new PermissionBoundaryError({
|
||||||
|
message: constructPermissionErrorMessage(
|
||||||
|
"Failed to create identity project membership",
|
||||||
|
shouldUseNewPrivilegeSystem,
|
||||||
|
ProjectPermissionIdentityActions.GrantPrivileges,
|
||||||
|
ProjectPermissionSub.Identity
|
||||||
|
),
|
||||||
|
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onUpdateMembershipIdentityGuard: TMembershipIdentityScopeFactory["onUpdateMembershipIdentityGuard"] = async (
|
||||||
|
dto
|
||||||
|
) => {
|
||||||
|
const scope = getScopeField(dto.scopeData);
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor: dto.permission.type,
|
||||||
|
actorId: dto.permission.id,
|
||||||
|
actionProjectType: ActionProjectType.Any,
|
||||||
|
actorAuthMethod: dto.permission.authMethod,
|
||||||
|
projectId: scope.value,
|
||||||
|
actorOrgId: dto.permission.orgId
|
||||||
|
});
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionIdentityActions.Edit,
|
||||||
|
ProjectPermissionSub.Identity
|
||||||
|
);
|
||||||
|
|
||||||
|
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(dto.permission.orgId);
|
||||||
|
const permissionRoles = await permissionService.getProjectPermissionByRoles(
|
||||||
|
dto.data.roles.map((el) => el.role),
|
||||||
|
scope.value
|
||||||
|
);
|
||||||
|
for (const permissionRole of permissionRoles) {
|
||||||
|
if (permissionRole?.role?.name !== ProjectMembershipRole.NoAccess) {
|
||||||
|
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||||
|
shouldUseNewPrivilegeSystem,
|
||||||
|
ProjectPermissionIdentityActions.GrantPrivileges,
|
||||||
|
ProjectPermissionSub.Identity,
|
||||||
|
permission,
|
||||||
|
permissionRole.permission
|
||||||
|
);
|
||||||
|
if (!permissionBoundary.isValid)
|
||||||
|
throw new PermissionBoundaryError({
|
||||||
|
message: constructPermissionErrorMessage(
|
||||||
|
"Failed to create identity project membership",
|
||||||
|
shouldUseNewPrivilegeSystem,
|
||||||
|
ProjectPermissionIdentityActions.GrantPrivileges,
|
||||||
|
ProjectPermissionSub.Identity
|
||||||
|
),
|
||||||
|
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDeleteMembershipIdentityGuard: TMembershipIdentityScopeFactory["onDeleteMembershipIdentityGuard"] = async (
|
||||||
|
dto
|
||||||
|
) => {
|
||||||
|
const scope = getScopeField(dto.scopeData);
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor: dto.permission.type,
|
||||||
|
actorId: dto.permission.id,
|
||||||
|
actionProjectType: ActionProjectType.Any,
|
||||||
|
actorAuthMethod: dto.permission.authMethod,
|
||||||
|
projectId: scope.value,
|
||||||
|
actorOrgId: dto.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionIdentityActions.Delete,
|
||||||
|
ProjectPermissionSub.Identity
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onListMembershipIdentityGuard: TMembershipIdentityScopeFactory["onListMembershipIdentityGuard"] = async (
|
||||||
|
dto
|
||||||
|
) => {
|
||||||
|
const scope = getScopeField(dto.scopeData);
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor: dto.permission.type,
|
||||||
|
actorId: dto.permission.id,
|
||||||
|
actionProjectType: ActionProjectType.Any,
|
||||||
|
actorAuthMethod: dto.permission.authMethod,
|
||||||
|
projectId: scope.value,
|
||||||
|
actorOrgId: dto.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionIdentityActions.Read,
|
||||||
|
ProjectPermissionSub.Identity
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onGetMembershipIdentityByIdentityIdGuard: TMembershipIdentityScopeFactory["onGetMembershipIdentityByIdentityIdGuard"] =
|
||||||
|
async (dto) => {
|
||||||
|
const scope = getScopeField(dto.scopeData);
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor: dto.permission.type,
|
||||||
|
actorId: dto.permission.id,
|
||||||
|
actionProjectType: ActionProjectType.Any,
|
||||||
|
actorAuthMethod: dto.permission.authMethod,
|
||||||
|
projectId: scope.value,
|
||||||
|
actorOrgId: dto.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionIdentityActions.Read,
|
||||||
|
ProjectPermissionSub.Identity
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
onCreateMembershipIdentityGuard,
|
||||||
|
onUpdateMembershipIdentityGuard,
|
||||||
|
onDeleteMembershipIdentityGuard,
|
||||||
|
onListMembershipIdentityGuard,
|
||||||
|
onGetMembershipIdentityByIdentityIdGuard,
|
||||||
|
getScopeField,
|
||||||
|
getScopeDatabaseFields,
|
||||||
|
isCustomRole
|
||||||
|
};
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user