From c6cab7a5286e4f3faf0a926d0f92ffffc08f9dee Mon Sep 17 00:00:00 2001 From: = Date: Sat, 27 Sep 2025 15:44:44 +0530 Subject: [PATCH] feat:simplified permission service --- backend/src/@types/knex.d.ts | 4 +- backend/src/db/schemas/models.ts | 22 + .../ee/services/permission/permission-dal.ts | 934 ++++-------------- .../permission/permission-service-types.ts | 229 +---- .../services/permission/permission-service.ts | 804 ++++++--------- 5 files changed, 575 insertions(+), 1418 deletions(-) diff --git a/backend/src/@types/knex.d.ts b/backend/src/@types/knex.d.ts index a974d90b7..07fe2c97d 100644 --- a/backend/src/@types/knex.d.ts +++ b/backend/src/@types/knex.d.ts @@ -1334,12 +1334,12 @@ declare module "knex/types/tables" { [TableName.Namespace]: KnexOriginal.CompositeTableType; [TableName.Membership]: KnexOriginal.CompositeTableType; - [TableName.MembershipRoles]: KnexOriginal.CompositeTableType< + [TableName.MembershipRole]: KnexOriginal.CompositeTableType< TMembershipRoles, TMembershipRolesInsert, TMembershipRolesUpdate >; - [TableName.Roles]: KnexOriginal.CompositeTableType; + [TableName.Role]: KnexOriginal.CompositeTableType; [TableName.AdditionalPrivilege]: KnexOriginal.CompositeTableType< TAdditionalPrivileges, TAdditionalPrivilegesInsert, diff --git a/backend/src/db/schemas/models.ts b/backend/src/db/schemas/models.ts index b4321b669..a46d3ea96 100644 --- a/backend/src/db/schemas/models.ts +++ b/backend/src/db/schemas/models.ts @@ -314,3 +314,25 @@ export enum SortDirection { ASC = "asc", DESC = "desc" } + +export enum AccessScope { + Organization = "organization", + Namespace = "namespace", + Project = "project" +} + +export type AccessScopeData = + | { + scope: AccessScope.Organization; + orgId: string; + } + | { + scope: AccessScope.Namespace; + orgId: string; + namespaceId: string; + } + | { + scope: AccessScope.Project; + orgId: string; + projectId: string; + }; diff --git a/backend/src/ee/services/permission/permission-dal.ts b/backend/src/ee/services/permission/permission-dal.ts index cdf55127f..7fb538e1f 100644 --- a/backend/src/ee/services/permission/permission-dal.ts +++ b/backend/src/ee/services/permission/permission-dal.ts @@ -1,181 +1,53 @@ +import { Knex } from "knex"; import { z } from "zod"; import { TDbClient } from "@app/db"; import { + AccessScope, + AccessScopeData, IdentityProjectMembershipRoleSchema, - OrgMembershipRole, - OrgMembershipsSchema, + MembershipsSchema, TableName, - TIdentityOrgMemberships, + TMemberships, TProjectRoles, TProjects } from "@app/db/schemas"; import { DatabaseError } from "@app/lib/errors"; import { selectAllTableCols, sqlNestRelationships } from "@app/lib/knex"; +import { ActorType } from "@app/services/auth/auth-type"; + +interface TPermissionDataReturn extends TMemberships { + roles: { + id: string; + createdAt: Date; + updatedAt: Date; + isTemporary: boolean; + role: string; + temporaryRange?: string | null | undefined; + permissions?: unknown; + customRoleId?: string | null | undefined; + temporaryMode?: string | null | undefined; + temporaryAccessStartTime?: Date | null | undefined; + temporaryAccessEndTime?: Date | null | undefined; + customRoleSlug?: string | null | undefined; + }[]; + additionalPrivileges: { + id: string; + permissions: unknown; + temporaryRange: string | null | undefined; + temporaryMode: string | null | undefined; + temporaryAccessEndTime: Date | null | undefined; + temporaryAccessStartTime: Date | null | undefined; + isTemporary: boolean; + }[]; + metadata: { + id: string; + key: string; + value: string; + }[]; +} export interface TPermissionDALFactory { - getOrgPermission: ( - userId: string, - orgId: string - ) => Promise< - { - status: string; - orgId: string; - id: string; - createdAt: Date; - updatedAt: Date; - role: string; - isActive: boolean; - shouldUseNewPrivilegeSystem: boolean; - bypassOrgAuthEnabled: boolean; - permissions?: unknown; - userId?: string | null | undefined; - roleId?: string | null | undefined; - inviteEmail?: string | null | undefined; - projectFavorites?: string[] | null | undefined; - customRoleSlug?: string | null | undefined; - orgAuthEnforced?: boolean | null | undefined; - orgGoogleSsoAuthEnforced: boolean; - } & { - groups: { - id: string; - updatedAt: Date; - createdAt: Date; - role: string; - roleId: string | null | undefined; - customRolePermission: unknown; - name: string; - slug: string; - orgId: string; - }[]; - } - >; - getOrgIdentityPermission: ( - identityId: string, - orgId: string - ) => Promise< - | (TIdentityOrgMemberships & { - orgAuthEnforced: boolean | null | undefined; - shouldUseNewPrivilegeSystem: boolean; - permissions?: unknown; - }) - | undefined - >; - getProjectPermission: ( - userId: string, - projectId: string - ) => Promise< - | { - roles: { - id: string; - role: string; - customRoleSlug: string; - permissions: unknown; - temporaryRange: string | null | undefined; - temporaryMode: string | null | undefined; - temporaryAccessStartTime: Date | null | undefined; - temporaryAccessEndTime: Date | null | undefined; - isTemporary: boolean; - }[]; - additionalPrivileges: { - id: string; - permissions: unknown; - temporaryRange: string | null | undefined; - temporaryMode: string | null | undefined; - temporaryAccessStartTime: Date | null | undefined; - temporaryAccessEndTime: Date | null | undefined; - isTemporary: boolean; - }[]; - orgId: string; - orgAuthEnforced: boolean | null | undefined; - orgGoogleSsoAuthEnforced: boolean; - orgRole: OrgMembershipRole; - userId: string; - projectId: string; - username: string; - projectType?: string | null; - id: string; - createdAt: Date; - updatedAt: Date; - shouldUseNewPrivilegeSystem: boolean; - bypassOrgAuthEnabled: boolean; - metadata: { - id: string; - key: string; - value: string; - }[]; - userGroupRoles: { - id: string; - role: string; - customRoleSlug: string; - permissions: unknown; - temporaryRange: string | null | undefined; - temporaryMode: string | null | undefined; - temporaryAccessStartTime: Date | null | undefined; - temporaryAccessEndTime: Date | null | undefined; - isTemporary: boolean; - }[]; - projecMembershiptRoles: { - id: string; - role: string; - customRoleSlug: string; - permissions: unknown; - temporaryRange: string | null | undefined; - temporaryMode: string | null | undefined; - temporaryAccessStartTime: Date | null | undefined; - temporaryAccessEndTime: Date | null | undefined; - isTemporary: boolean; - }[]; - } - | undefined - >; - getProjectIdentityPermission: ( - identityId: string, - projectId: string - ) => Promise< - | { - roles: { - id: string; - createdAt: Date; - updatedAt: Date; - isTemporary: boolean; - role: string; - projectMembershipId: string; - temporaryRange?: string | null | undefined; - permissions?: unknown; - customRoleId?: string | null | undefined; - temporaryMode?: string | null | undefined; - temporaryAccessStartTime?: Date | null | undefined; - temporaryAccessEndTime?: Date | null | undefined; - customRoleSlug?: string | null | undefined; - }[]; - additionalPrivileges: { - id: string; - permissions: unknown; - temporaryRange: string | null | undefined; - temporaryMode: string | null | undefined; - temporaryAccessEndTime: Date | null | undefined; - temporaryAccessStartTime: Date | null | undefined; - isTemporary: boolean; - }[]; - id: string; - identityId: string; - username: string; - projectId: string; - createdAt: Date; - updatedAt: Date; - orgId: string; - projectType?: string | null; - shouldUseNewPrivilegeSystem: boolean; - orgAuthEnforced: boolean; - metadata: { - id: string; - key: string; - value: string; - }[]; - } - | undefined - >; getProjectUserPermissions: (projectId: string) => Promise< { roles: { @@ -310,128 +182,181 @@ export interface TPermissionDALFactory { }[]; }[] >; + getPermission: (dto: { + scopeData: AccessScopeData; + actorId: string; + actorType: ActorType.IDENTITY | ActorType.USER; + tx?: Knex; + }) => Promise; } export const permissionDALFactory = (db: TDbClient): TPermissionDALFactory => { - const getOrgPermission: TPermissionDALFactory["getOrgPermission"] = async (userId: string, orgId: string) => { + const getPermission: TPermissionDALFactory["getPermission"] = async ({ scopeData, tx, actorId, actorType }) => { try { - const groupSubQuery = db(TableName.Groups) - .where(`${TableName.Groups}.orgId`, orgId) - .join(TableName.UserGroupMembership, (queryBuilder) => { - queryBuilder - .on(`${TableName.UserGroupMembership}.groupId`, `${TableName.Groups}.id`) - .andOn(`${TableName.UserGroupMembership}.userId`, db.raw("?", [userId])); - }) - .leftJoin(TableName.OrgRoles, `${TableName.Groups}.roleId`, `${TableName.OrgRoles}.id`) - .select( - db.ref("id").withSchema(TableName.Groups).as("groupId"), - db.ref("orgId").withSchema(TableName.Groups).as("groupOrgId"), - db.ref("name").withSchema(TableName.Groups).as("groupName"), - db.ref("slug").withSchema(TableName.Groups).as("groupSlug"), - db.ref("role").withSchema(TableName.Groups).as("groupRole"), - db.ref("roleId").withSchema(TableName.Groups).as("groupRoleId"), - db.ref("createdAt").withSchema(TableName.Groups).as("groupCreatedAt"), - db.ref("updatedAt").withSchema(TableName.Groups).as("groupUpdatedAt"), - db.ref("permissions").withSchema(TableName.OrgRoles).as("groupCustomRolePermission") - ); + // akhilmhdh: when group has another group like sub group we would need recursively go down + const userGroupSubquery = (tx || db)(TableName.Groups) + .leftJoin(TableName.UserGroupMembership, `${TableName.UserGroupMembership}.groupId`, `${TableName.Groups}.id`) + .where(`${TableName.Groups}.orgId`, scopeData.orgId) + .where(`${TableName.UserGroupMembership}.userId`, actorId) + .select(db.ref("id").withSchema(TableName.Groups)); - const membership = await db - .replicaNode()(TableName.OrgMembership) - .where(`${TableName.OrgMembership}.orgId`, orgId) - .where(`${TableName.OrgMembership}.userId`, userId) - .leftJoin(TableName.OrgRoles, `${TableName.OrgRoles}.id`, `${TableName.OrgMembership}.roleId`) - .leftJoin[0]>( - groupSubQuery.as("userGroups"), - "userGroups.groupOrgId", - db.raw("?", [orgId]) + const docs = await (tx || db) + .replicaNode()(TableName.Membership) + .join(TableName.MembershipRole, `${TableName.Membership}.id`, `${TableName.MembershipRole}.membershipId`) + .leftJoin(TableName.Role, `${TableName.MembershipRole}.customRoleId`, `${TableName.Role}.id`) + .leftJoin( + TableName.AdditionalPrivilege, + `${TableName.Membership}.id`, + `${TableName.AdditionalPrivilege}.membershipId` ) - .join(TableName.Organization, `${TableName.Organization}.id`, `${TableName.OrgMembership}.orgId`) + .leftJoin(TableName.IdentityMetadata, (queryBuilder) => { + if (actorType === ActorType.USER) { + void queryBuilder + .on(`${TableName.Membership}.actorUserId`, `${TableName.IdentityMetadata}.userId`) + .andOn(`${TableName.Membership}.scopeOrgId`, `${TableName.IdentityMetadata}.orgId`); + } else if (actorType === ActorType.IDENTITY) { + void queryBuilder + .on(`${TableName.Membership}.actorIdentityId`, `${TableName.IdentityMetadata}.identityId`) + .andOn(`${TableName.Membership}.scopeOrgId`, `${TableName.IdentityMetadata}.orgId`); + } + }) + .where(`${TableName.Membership}.scopeOrgId`, scopeData.orgId) + .where((qb) => { + if (actorType === ActorType.USER) { + void qb + .where(`${TableName.Membership}.actorUserId`, actorId) + .orWhereIn(`${TableName.Membership}.actorGroupId`, userGroupSubquery); + } else if (actorType === ActorType.IDENTITY) { + void qb.where(`${TableName.Membership}.actorIdentityId`, actorId); + } + + 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); + } + }) + .select(selectAllTableCols(TableName.Membership)) .select( - selectAllTableCols(TableName.OrgMembership), - db.ref("shouldUseNewPrivilegeSystem").withSchema(TableName.Organization), - db.ref("slug").withSchema(TableName.OrgRoles).withSchema(TableName.OrgRoles).as("customRoleSlug"), - db.ref("permissions").withSchema(TableName.OrgRoles), - db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"), - db.ref("googleSsoAuthEnforced").withSchema(TableName.Organization).as("orgGoogleSsoAuthEnforced"), - db.ref("bypassOrgAuthEnabled").withSchema(TableName.Organization).as("bypassOrgAuthEnabled"), - db.ref("groupId").withSchema("userGroups"), - db.ref("groupOrgId").withSchema("userGroups"), - db.ref("groupName").withSchema("userGroups"), - db.ref("groupSlug").withSchema("userGroups"), - db.ref("groupRole").withSchema("userGroups"), - db.ref("groupRoleId").withSchema("userGroups"), - db.ref("groupCreatedAt").withSchema("userGroups"), - db.ref("groupUpdatedAt").withSchema("userGroups"), - db.ref("groupCustomRolePermission").withSchema("userGroups") + db.ref("slug").withSchema(TableName.Role).as("roleSlug"), + db.ref("permissions").withSchema(TableName.Role).as("customRolePermission"), + 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.AdditionalPrivilege).as("additionalPrivilegeId"), + db.ref("name").withSchema(TableName.AdditionalPrivilege).as("additionalPrivilegeName"), + db.ref("permissions").withSchema(TableName.AdditionalPrivilege).as("additionalPrivilegePermissions"), + db.ref("id").withSchema(TableName.AdditionalPrivilege).as("additionalPrivilegeId"), + db.ref("temporaryMode").withSchema(TableName.AdditionalPrivilege).as("additionalPrivilegeTemporaryMode"), + db.ref("isTemporary").withSchema(TableName.AdditionalPrivilege).as("additionalPrivilegeIsTemporary"), + db.ref("temporaryRange").withSchema(TableName.AdditionalPrivilege).as("additionalPrivilegeTemporaryRange"), + db + .ref("temporaryAccessStartTime") + .withSchema(TableName.AdditionalPrivilege) + .as("additionalPrivilegeTemporaryAccessStartTime"), + db + .ref("temporaryAccessEndTime") + .withSchema(TableName.AdditionalPrivilege) + .as("additionalPrivilegeTemporaryAccessEndTime"), + db.ref("createdAt").withSchema(TableName.AdditionalPrivilege).as("additionalPrivilegeCreatedAt"), + db.ref("updatedAt").withSchema(TableName.AdditionalPrivilege).as("additionalPrivilegeUpdatedAt"), + db.ref("id").withSchema(TableName.IdentityMetadata).as("metadataId"), + db.ref("key").withSchema(TableName.IdentityMetadata).as("metadataKey"), + db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue") ); - const [formatedDoc] = sqlNestRelationships({ - data: membership, + const data = sqlNestRelationships({ + data: docs, key: "id", - parentMapper: (el) => - OrgMembershipsSchema.extend({ - permissions: z.unknown(), - orgAuthEnforced: z.boolean().optional().nullable(), - orgGoogleSsoAuthEnforced: z.boolean(), - bypassOrgAuthEnabled: z.boolean(), - customRoleSlug: z.string().optional().nullable(), - shouldUseNewPrivilegeSystem: z.boolean() - }).parse(el), + parentMapper: (el) => MembershipsSchema.parse(el), childrenMapper: [ { - key: "groupId", - label: "groups" as const, + key: "additionalPrivilegeId", + label: "additionalPrivileges" as const, mapper: ({ - groupId, - groupUpdatedAt, - groupCreatedAt, - groupRole, - groupRoleId, - groupCustomRolePermission, - groupName, - groupSlug, - groupOrgId + additionalPrivilegeId, + additionalPrivilegePermissions, + additionalPrivilegeIsTemporary, + additionalPrivilegeTemporaryMode, + additionalPrivilegeTemporaryRange, + additionalPrivilegeTemporaryAccessEndTime, + additionalPrivilegeTemporaryAccessStartTime, + additionalPrivilegeCreatedAt, + additionalPrivilegeUpdatedAt }) => ({ - id: groupId, - updatedAt: groupUpdatedAt, - createdAt: groupCreatedAt, - role: groupRole, - roleId: groupRoleId, - customRolePermission: groupCustomRolePermission, - name: groupName, - slug: groupSlug, - orgId: groupOrgId + id: additionalPrivilegeId, + permissions: additionalPrivilegePermissions, + temporaryRange: additionalPrivilegeTemporaryRange, + temporaryMode: additionalPrivilegeTemporaryMode, + temporaryAccessStartTime: additionalPrivilegeTemporaryAccessStartTime, + temporaryAccessEndTime: additionalPrivilegeTemporaryAccessEndTime, + isTemporary: additionalPrivilegeIsTemporary, + createdAt: additionalPrivilegeCreatedAt, + updatedAt: additionalPrivilegeUpdatedAt + }) + }, + { + key: "membershipRoleId", + label: "roles" as const, + mapper: ({ + roleSlug, + customRolePermission, + membershipRoleId, + membershipRole, + membershipRoleIsTemporary, + membershipRoleTemporaryMode, + membershipRoleTemporaryRange, + membershipRoleTemporaryAccessEndTime, + membershipRoleTemporaryAccessStartTime, + membershipRoleCreatedAt, + membershipRoleUpdatedAt + }) => ({ + id: membershipRoleId, + role: membershipRole, + permissions: customRolePermission, + 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 formatedDoc; + return data; } catch (error) { - throw new DatabaseError({ error, name: "GetOrgPermission" }); - } - }; - - const getOrgIdentityPermission: TPermissionDALFactory["getOrgIdentityPermission"] = async ( - identityId: string, - orgId: string - ) => { - try { - const membership = await db - .replicaNode()(TableName.IdentityOrgMembership) - .leftJoin(TableName.OrgRoles, `${TableName.IdentityOrgMembership}.roleId`, `${TableName.OrgRoles}.id`) - .join(TableName.Organization, `${TableName.IdentityOrgMembership}.orgId`, `${TableName.Organization}.id`) - .where("identityId", identityId) - .where(`${TableName.IdentityOrgMembership}.orgId`, orgId) - .select(selectAllTableCols(TableName.IdentityOrgMembership)) - .select(db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced")) - .select("permissions") - .select(db.ref("shouldUseNewPrivilegeSystem").withSchema(TableName.Organization)) - .first(); - - return membership; - } catch (error) { - throw new DatabaseError({ error, name: "GetOrgIdentityPermission" }); + throw new DatabaseError({ error, name: "Get Permission" }); } }; @@ -841,315 +766,6 @@ export const permissionDALFactory = (db: TDbClient): TPermissionDALFactory => { } }; - const getProjectPermission: TPermissionDALFactory["getProjectPermission"] = async ( - userId: string, - projectId: string - ) => { - try { - const subQueryUserGroups = db(TableName.UserGroupMembership).where("userId", userId).select("groupId"); - const docs = await db - .replicaNode()(TableName.Users) - .where(`${TableName.Users}.id`, userId) - .leftJoin(TableName.GroupProjectMembership, (queryBuilder) => { - void queryBuilder - .on(`${TableName.GroupProjectMembership}.projectId`, db.raw("?", [projectId])) - // @ts-expect-error akhilmhdh: this is valid knexjs query. Its just ts type argument is missing it - .andOnIn(`${TableName.GroupProjectMembership}.groupId`, subQueryUserGroups); - }) - .leftJoin( - TableName.GroupProjectMembershipRole, - `${TableName.GroupProjectMembershipRole}.projectMembershipId`, - `${TableName.GroupProjectMembership}.id` - ) - .leftJoin( - { groupCustomRoles: TableName.ProjectRoles }, - `${TableName.GroupProjectMembershipRole}.customRoleId`, - `groupCustomRoles.id` - ) - .leftJoin(TableName.ProjectMembership, (queryBuilder) => { - void queryBuilder - .on(`${TableName.ProjectMembership}.projectId`, db.raw("?", [projectId])) - .andOn(`${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`); - }) - .leftJoin( - TableName.ProjectUserMembershipRole, - `${TableName.ProjectUserMembershipRole}.projectMembershipId`, - `${TableName.ProjectMembership}.id` - ) - .leftJoin( - TableName.ProjectRoles, - `${TableName.ProjectUserMembershipRole}.customRoleId`, - `${TableName.ProjectRoles}.id` - ) - .leftJoin(TableName.ProjectUserAdditionalPrivilege, (queryBuilder) => { - void queryBuilder - .on(`${TableName.ProjectUserAdditionalPrivilege}.projectId`, db.raw("?", [projectId])) - .andOn(`${TableName.ProjectUserAdditionalPrivilege}.userId`, `${TableName.Users}.id`); - }) - .join(TableName.Project, `${TableName.Project}.id`, db.raw("?", [projectId])) - .join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`) - .join(TableName.OrgMembership, (qb) => { - void qb - .on(`${TableName.OrgMembership}.userId`, `${TableName.Users}.id`) - .andOn(`${TableName.OrgMembership}.orgId`, `${TableName.Organization}.id`); - }) - .leftJoin(TableName.IdentityMetadata, (queryBuilder) => { - void queryBuilder - .on(`${TableName.Users}.id`, `${TableName.IdentityMetadata}.userId`) - .andOn(`${TableName.Organization}.id`, `${TableName.IdentityMetadata}.orgId`); - }) - .select( - db.ref("id").withSchema(TableName.Users).as("userId"), - db.ref("username").withSchema(TableName.Users).as("username"), - // groups specific - db.ref("id").withSchema(TableName.GroupProjectMembership).as("groupMembershipId"), - db.ref("createdAt").withSchema(TableName.GroupProjectMembership).as("groupMembershipCreatedAt"), - db.ref("updatedAt").withSchema(TableName.GroupProjectMembership).as("groupMembershipUpdatedAt"), - db.ref("slug").withSchema("groupCustomRoles").as("userGroupProjectMembershipRoleCustomRoleSlug"), - db.ref("permissions").withSchema("groupCustomRoles").as("userGroupProjectMembershipRolePermission"), - db.ref("id").withSchema(TableName.GroupProjectMembershipRole).as("userGroupProjectMembershipRoleId"), - db.ref("role").withSchema(TableName.GroupProjectMembershipRole).as("userGroupProjectMembershipRole"), - db - .ref("customRoleId") - .withSchema(TableName.GroupProjectMembershipRole) - .as("userGroupProjectMembershipRoleCustomRoleId"), - db - .ref("isTemporary") - .withSchema(TableName.GroupProjectMembershipRole) - .as("userGroupProjectMembershipRoleIsTemporary"), - db - .ref("temporaryMode") - .withSchema(TableName.GroupProjectMembershipRole) - .as("userGroupProjectMembershipRoleTemporaryMode"), - db - .ref("temporaryRange") - .withSchema(TableName.GroupProjectMembershipRole) - .as("userGroupProjectMembershipRoleTemporaryRange"), - db - .ref("temporaryAccessStartTime") - .withSchema(TableName.GroupProjectMembershipRole) - .as("userGroupProjectMembershipRoleTemporaryAccessStartTime"), - db - .ref("temporaryAccessEndTime") - .withSchema(TableName.GroupProjectMembershipRole) - .as("userGroupProjectMembershipRoleTemporaryAccessEndTime"), - // user specific - db.ref("id").withSchema(TableName.ProjectMembership).as("membershipId"), - db.ref("createdAt").withSchema(TableName.ProjectMembership).as("membershipCreatedAt"), - db.ref("updatedAt").withSchema(TableName.ProjectMembership).as("membershipUpdatedAt"), - db.ref("slug").withSchema(TableName.ProjectRoles).as("userProjectMembershipRoleCustomRoleSlug"), - db.ref("permissions").withSchema(TableName.ProjectRoles).as("userProjectCustomRolePermission"), - db.ref("id").withSchema(TableName.ProjectUserMembershipRole).as("userProjectMembershipRoleId"), - db.ref("role").withSchema(TableName.ProjectUserMembershipRole).as("userProjectMembershipRole"), - db - .ref("temporaryMode") - .withSchema(TableName.ProjectUserMembershipRole) - .as("userProjectMembershipRoleTemporaryMode"), - db - .ref("isTemporary") - .withSchema(TableName.ProjectUserMembershipRole) - .as("userProjectMembershipRoleIsTemporary"), - db - .ref("temporaryRange") - .withSchema(TableName.ProjectUserMembershipRole) - .as("userProjectMembershipRoleTemporaryRange"), - db - .ref("temporaryAccessStartTime") - .withSchema(TableName.ProjectUserMembershipRole) - .as("userProjectMembershipRoleTemporaryAccessStartTime"), - db - .ref("temporaryAccessEndTime") - .withSchema(TableName.ProjectUserMembershipRole) - .as("userProjectMembershipRoleTemporaryAccessEndTime"), - db.ref("id").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userAdditionalPrivilegesId"), - db - .ref("permissions") - .withSchema(TableName.ProjectUserAdditionalPrivilege) - .as("userAdditionalPrivilegesPermissions"), - db - .ref("temporaryMode") - .withSchema(TableName.ProjectUserAdditionalPrivilege) - .as("userAdditionalPrivilegesTemporaryMode"), - db - .ref("isTemporary") - .withSchema(TableName.ProjectUserAdditionalPrivilege) - .as("userAdditionalPrivilegesIsTemporary"), - db - .ref("temporaryRange") - .withSchema(TableName.ProjectUserAdditionalPrivilege) - .as("userAdditionalPrivilegesTemporaryRange"), - db.ref("userId").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userAdditionalPrivilegesUserId"), - db - .ref("temporaryAccessStartTime") - .withSchema(TableName.ProjectUserAdditionalPrivilege) - .as("userAdditionalPrivilegesTemporaryAccessStartTime"), - db - .ref("temporaryAccessEndTime") - .withSchema(TableName.ProjectUserAdditionalPrivilege) - .as("userAdditionalPrivilegesTemporaryAccessEndTime"), - // general - 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("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"), - db.ref("googleSsoAuthEnforced").withSchema(TableName.Organization).as("orgGoogleSsoAuthEnforced"), - db.ref("bypassOrgAuthEnabled").withSchema(TableName.Organization).as("bypassOrgAuthEnabled"), - db.ref("role").withSchema(TableName.OrgMembership).as("orgRole"), - db.ref("orgId").withSchema(TableName.Project), - db.ref("type").withSchema(TableName.Project).as("projectType"), - db.ref("id").withSchema(TableName.Project).as("projectId"), - db.ref("shouldUseNewPrivilegeSystem").withSchema(TableName.Organization) - ); - - const [userPermission] = sqlNestRelationships({ - data: docs, - key: "projectId", - parentMapper: ({ - orgId, - username, - orgAuthEnforced, - orgGoogleSsoAuthEnforced, - orgRole, - membershipId, - groupMembershipId, - membershipCreatedAt, - groupMembershipCreatedAt, - groupMembershipUpdatedAt, - membershipUpdatedAt, - projectType, - shouldUseNewPrivilegeSystem, - bypassOrgAuthEnabled - }) => ({ - orgId, - orgAuthEnforced, - orgGoogleSsoAuthEnforced, - orgRole: orgRole as OrgMembershipRole, - userId, - projectId, - username, - projectType, - id: membershipId || groupMembershipId, - createdAt: membershipCreatedAt || groupMembershipCreatedAt, - updatedAt: membershipUpdatedAt || groupMembershipUpdatedAt, - shouldUseNewPrivilegeSystem, - bypassOrgAuthEnabled - }), - childrenMapper: [ - { - key: "userGroupProjectMembershipRoleId", - label: "userGroupRoles" as const, - mapper: ({ - userGroupProjectMembershipRoleId, - userGroupProjectMembershipRole, - userGroupProjectMembershipRolePermission, - userGroupProjectMembershipRoleCustomRoleSlug, - userGroupProjectMembershipRoleIsTemporary, - userGroupProjectMembershipRoleTemporaryMode, - userGroupProjectMembershipRoleTemporaryAccessEndTime, - userGroupProjectMembershipRoleTemporaryAccessStartTime, - userGroupProjectMembershipRoleTemporaryRange - }) => ({ - id: userGroupProjectMembershipRoleId, - role: userGroupProjectMembershipRole, - customRoleSlug: userGroupProjectMembershipRoleCustomRoleSlug, - permissions: userGroupProjectMembershipRolePermission, - temporaryRange: userGroupProjectMembershipRoleTemporaryRange, - temporaryMode: userGroupProjectMembershipRoleTemporaryMode, - temporaryAccessStartTime: userGroupProjectMembershipRoleTemporaryAccessStartTime, - temporaryAccessEndTime: userGroupProjectMembershipRoleTemporaryAccessEndTime, - isTemporary: userGroupProjectMembershipRoleIsTemporary - }) - }, - { - key: "userProjectMembershipRoleId", - label: "projecMembershiptRoles" as const, - mapper: ({ - userProjectMembershipRoleId, - userProjectMembershipRole, - userProjectCustomRolePermission, - userProjectMembershipRoleIsTemporary, - userProjectMembershipRoleTemporaryMode, - userProjectMembershipRoleTemporaryRange, - userProjectMembershipRoleTemporaryAccessEndTime, - userProjectMembershipRoleTemporaryAccessStartTime, - userProjectMembershipRoleCustomRoleSlug - }) => ({ - id: userProjectMembershipRoleId, - role: userProjectMembershipRole, - customRoleSlug: userProjectMembershipRoleCustomRoleSlug, - permissions: userProjectCustomRolePermission, - temporaryRange: userProjectMembershipRoleTemporaryRange, - temporaryMode: userProjectMembershipRoleTemporaryMode, - temporaryAccessStartTime: userProjectMembershipRoleTemporaryAccessStartTime, - temporaryAccessEndTime: userProjectMembershipRoleTemporaryAccessEndTime, - isTemporary: userProjectMembershipRoleIsTemporary - }) - }, - { - key: "userAdditionalPrivilegesId", - label: "additionalPrivileges" as const, - mapper: ({ - userAdditionalPrivilegesId, - userAdditionalPrivilegesPermissions, - userAdditionalPrivilegesIsTemporary, - userAdditionalPrivilegesTemporaryMode, - userAdditionalPrivilegesTemporaryRange, - userAdditionalPrivilegesTemporaryAccessEndTime, - userAdditionalPrivilegesTemporaryAccessStartTime - }) => ({ - id: userAdditionalPrivilegesId, - permissions: userAdditionalPrivilegesPermissions, - temporaryRange: userAdditionalPrivilegesTemporaryRange, - temporaryMode: userAdditionalPrivilegesTemporaryMode, - temporaryAccessStartTime: userAdditionalPrivilegesTemporaryAccessStartTime, - temporaryAccessEndTime: userAdditionalPrivilegesTemporaryAccessEndTime, - isTemporary: userAdditionalPrivilegesIsTemporary - }) - }, - { - key: "metadataId", - label: "metadata" as const, - mapper: ({ metadataKey, metadataValue, metadataId }) => ({ - id: metadataId, - key: metadataKey, - value: metadataValue - }) - } - ] - }); - - if (!userPermission) return undefined; - if (!userPermission?.userGroupRoles?.[0] && !userPermission?.projecMembershiptRoles?.[0]) return undefined; - - // when introducting cron mode change it here - const activeRoles = - userPermission?.projecMembershiptRoles?.filter( - ({ isTemporary, temporaryAccessEndTime }) => - !isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime) - ) ?? []; - - const activeGroupRoles = - userPermission?.userGroupRoles?.filter( - ({ isTemporary, temporaryAccessEndTime }) => - !isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime) - ) ?? []; - - const activeAdditionalPrivileges = - userPermission?.additionalPrivileges?.filter( - ({ isTemporary, temporaryAccessEndTime }) => - !isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime) - ) ?? []; - - return { - ...userPermission, - roles: [...activeRoles, ...activeGroupRoles], - additionalPrivileges: activeAdditionalPrivileges - }; - } catch (error) { - throw new DatabaseError({ error, name: "GetProjectPermission" }); - } - }; - const getProjectIdentityPermissions: TPermissionDALFactory["getProjectIdentityPermissions"] = async ( projectId: string ) => { @@ -1309,170 +925,10 @@ export const permissionDALFactory = (db: TDbClient): TPermissionDALFactory => { } }; - const getProjectIdentityPermission: TPermissionDALFactory["getProjectIdentityPermission"] = async ( - identityId, - projectId - ) => { - try { - const docs = await db - .replicaNode()(TableName.IdentityProjectMembership) - .join( - TableName.IdentityProjectMembershipRole, - `${TableName.IdentityProjectMembershipRole}.projectMembershipId`, - `${TableName.IdentityProjectMembership}.id` - ) - .join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.IdentityProjectMembership}.identityId`) - .leftJoin( - TableName.ProjectRoles, - `${TableName.IdentityProjectMembershipRole}.customRoleId`, - `${TableName.ProjectRoles}.id` - ) - .leftJoin( - TableName.IdentityProjectAdditionalPrivilege, - `${TableName.IdentityProjectAdditionalPrivilege}.projectMembershipId`, - `${TableName.IdentityProjectMembership}.id` - ) - .join( - // Join the Project table to later select orgId - TableName.Project, - `${TableName.IdentityProjectMembership}.projectId`, - `${TableName.Project}.id` - ) - .join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`) - .leftJoin(TableName.IdentityMetadata, (queryBuilder) => { - void queryBuilder - .on(`${TableName.Identity}.id`, `${TableName.IdentityMetadata}.identityId`) - .andOn(`${TableName.Project}.orgId`, `${TableName.IdentityMetadata}.orgId`); - }) - .where(`${TableName.IdentityProjectMembership}.identityId`, identityId) - .where(`${TableName.IdentityProjectMembership}.projectId`, projectId) - .select(selectAllTableCols(TableName.IdentityProjectMembershipRole)) - .select( - db.ref("id").withSchema(TableName.IdentityProjectMembership).as("membershipId"), - db.ref("name").withSchema(TableName.Identity).as("identityName"), - db.ref("orgId").withSchema(TableName.Project).as("orgId"), // Now you can select orgId from Project - db.ref("type").withSchema(TableName.Project).as("projectType"), - db.ref("createdAt").withSchema(TableName.IdentityProjectMembership).as("membershipCreatedAt"), - db.ref("updatedAt").withSchema(TableName.IdentityProjectMembership).as("membershipUpdatedAt"), - db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug"), - db.ref("permissions").withSchema(TableName.ProjectRoles), - db.ref("shouldUseNewPrivilegeSystem").withSchema(TableName.Organization), - db.ref("id").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApId"), - db.ref("permissions").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApPermissions"), - db - .ref("temporaryMode") - .withSchema(TableName.IdentityProjectAdditionalPrivilege) - .as("identityApTemporaryMode"), - db.ref("isTemporary").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApIsTemporary"), - db - .ref("temporaryRange") - .withSchema(TableName.IdentityProjectAdditionalPrivilege) - .as("identityApTemporaryRange"), - db - .ref("temporaryAccessStartTime") - .withSchema(TableName.IdentityProjectAdditionalPrivilege) - .as("identityApTemporaryAccessStartTime"), - db - .ref("temporaryAccessEndTime") - .withSchema(TableName.IdentityProjectAdditionalPrivilege) - .as("identityApTemporaryAccessEndTime"), - db.ref("id").withSchema(TableName.IdentityMetadata).as("metadataId"), - db.ref("key").withSchema(TableName.IdentityMetadata).as("metadataKey"), - db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue") - ); - - const permission = sqlNestRelationships({ - data: docs, - key: "membershipId", - parentMapper: ({ - membershipId, - membershipCreatedAt, - membershipUpdatedAt, - orgId, - identityName, - projectType, - shouldUseNewPrivilegeSystem - }) => ({ - id: membershipId, - identityId, - username: identityName, - projectId, - createdAt: membershipCreatedAt, - updatedAt: membershipUpdatedAt, - orgId, - projectType, - shouldUseNewPrivilegeSystem, - // just a prefilled value - orgAuthEnforced: false - }), - childrenMapper: [ - { - key: "id", - label: "roles" as const, - mapper: (data) => - IdentityProjectMembershipRoleSchema.extend({ - permissions: z.unknown(), - customRoleSlug: z.string().optional().nullable() - }).parse(data) - }, - { - key: "identityApId", - label: "additionalPrivileges" as const, - mapper: ({ - identityApId, - identityApPermissions, - identityApIsTemporary, - identityApTemporaryMode, - identityApTemporaryRange, - identityApTemporaryAccessEndTime, - identityApTemporaryAccessStartTime - }) => ({ - id: identityApId, - permissions: identityApPermissions, - temporaryRange: identityApTemporaryRange, - temporaryMode: identityApTemporaryMode, - temporaryAccessEndTime: identityApTemporaryAccessEndTime, - temporaryAccessStartTime: identityApTemporaryAccessStartTime, - isTemporary: identityApIsTemporary - }) - }, - { - key: "metadataId", - label: "metadata" as const, - mapper: ({ metadataKey, metadataValue, metadataId }) => ({ - id: metadataId, - key: metadataKey, - value: metadataValue - }) - } - ] - }); - - if (!permission?.[0]) return undefined; - - // when introducting cron mode change it here - const activeRoles = permission?.[0]?.roles.filter( - ({ isTemporary, temporaryAccessEndTime }) => - !isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime) - ); - const activeAdditionalPrivileges = permission?.[0]?.additionalPrivileges?.filter( - ({ isTemporary, temporaryAccessEndTime }) => - !isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime) - ); - - return { ...permission[0], roles: activeRoles, additionalPrivileges: activeAdditionalPrivileges }; - } catch (error) { - throw new DatabaseError({ error, name: "GetProjectIdentityPermission" }); - } - }; - return { - getOrgPermission, - getOrgIdentityPermission, - getProjectPermission, - getProjectIdentityPermission, getProjectUserPermissions, getProjectIdentityPermissions, - getProjectGroupPermissions + getProjectGroupPermissions, + getPermission }; }; diff --git a/backend/src/ee/services/permission/permission-service-types.ts b/backend/src/ee/services/permission/permission-service-types.ts index f25b84bd5..0519a71fa 100644 --- a/backend/src/ee/services/permission/permission-service-types.ts +++ b/backend/src/ee/services/permission/permission-service-types.ts @@ -1,8 +1,8 @@ -import { MongoAbility, RawRuleOf } from "@casl/ability"; +import { MongoAbility } from "@casl/ability"; import { MongoQuery } from "@ucast/mongo2js"; import { Knex } from "knex"; -import { ActionProjectType } from "@app/db/schemas"; +import { ActionProjectType, TMemberships } from "@app/db/schemas"; import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type"; import { OrgPermissionSet } from "./org-permission"; @@ -50,162 +50,22 @@ export type TGetProjectPermissionArg = { }; export type TPermissionServiceFactory = { - getUserOrgPermission: ( - userId: string, - orgId: string, - authMethod: ActorAuthMethod, - userOrgId?: string - ) => Promise<{ - permission: MongoAbility; - membership: { - status: string; - orgId: string; - id: string; - createdAt: Date; - updatedAt: Date; - role: string; - isActive: boolean; - shouldUseNewPrivilegeSystem: boolean; - bypassOrgAuthEnabled: boolean; - permissions?: unknown; - userId?: string | null | undefined; - roleId?: string | null | undefined; - inviteEmail?: string | null | undefined; - projectFavorites?: string[] | null | undefined; - customRoleSlug?: string | null | undefined; - orgAuthEnforced?: boolean | null | undefined; - } & { - groups: { - id: string; - updatedAt: Date; - createdAt: Date; - role: string; - roleId: string | null | undefined; - customRolePermission: unknown; - name: string; - slug: string; - orgId: string; - }[]; - }; - }>; getOrgPermission: ( type: ActorType, id: string, orgId: string, authMethod: ActorAuthMethod, actorOrgId: string | undefined - ) => Promise< - | { - permission: MongoAbility; - membership: { - status: string; - orgId: string; - id: string; - createdAt: Date; - updatedAt: Date; - role: string; - isActive: boolean; - shouldUseNewPrivilegeSystem: boolean; - bypassOrgAuthEnabled: boolean; - permissions?: unknown; - userId?: string | null | undefined; - roleId?: string | null | undefined; - inviteEmail?: string | null | undefined; - projectFavorites?: string[] | null | undefined; - customRoleSlug?: string | null | undefined; - orgAuthEnforced?: boolean | null | undefined; - } & { - groups: { - id: string; - updatedAt: Date; - createdAt: Date; - role: string; - roleId: string | null | undefined; - customRolePermission: unknown; - name: string; - slug: string; - orgId: string; - }[]; - }; - } - | { - permission: MongoAbility; - membership: { - id: string; - role: string; - createdAt: Date; - updatedAt: Date; - orgId: string; - roleId?: string | null | undefined; - permissions?: unknown; - identityId: string; - orgAuthEnforced: boolean | null | undefined; - shouldUseNewPrivilegeSystem: boolean; - }; - } - >; - getUserProjectPermission: ({ - userId, - projectId, - authMethod, - userOrgId, - actionProjectType - }: TGetUserProjectPermissionArg) => Promise<{ + ) => Promise<{ + permission: MongoAbility; + memberships: TMemberships[]; + }>; + getProjectPermission: (arg: TGetProjectPermissionArg) => Promise<{ permission: MongoAbility; - membership: { - id: string; - createdAt: Date; - updatedAt: Date; - userId: string; - projectId: string; - } & { - orgAuthEnforced: boolean | null | undefined; - orgId: string; - roles: Array<{ - role: string; - }>; - shouldUseNewPrivilegeSystem: boolean; - }; + memberships: TMemberships[]; hasRole: (role: string) => boolean; }>; - getProjectPermission: ( - arg: TGetProjectPermissionArg - ) => Promise< - T extends ActorType.SERVICE - ? { - permission: MongoAbility; - membership: { - shouldUseNewPrivilegeSystem: boolean; - }; - hasRole: (arg: string) => boolean; - } - : { - permission: MongoAbility; - membership: (T extends ActorType.USER - ? { - id: string; - createdAt: Date; - updatedAt: Date; - userId: string; - projectId: string; - } - : { - id: string; - createdAt: Date; - updatedAt: Date; - projectId: string; - identityId: string; - }) & { - orgAuthEnforced: boolean | null | undefined; - orgId: string; - roles: Array<{ - role: string; - }>; - shouldUseNewPrivilegeSystem: boolean; - }; - hasRole: (role: string) => boolean; - } - >; + // TODO(simp): check what is this doing getProjectPermissions: (projectId: string) => Promise<{ userPermissions: { permission: MongoAbility; @@ -226,55 +86,40 @@ export type TPermissionServiceFactory = { membershipId: string; }[]; }>; + // TODO(simp): switch to role dal later getOrgPermissionByRole: ( role: string, orgId: string - ) => Promise< - | { - permission: MongoAbility; - role: { - name: string; - orgId: string; - id: string; - createdAt: Date; - updatedAt: Date; - slug: string; - permissions?: unknown; - description?: string | null | undefined; - }; - } - | { - permission: MongoAbility; - role?: undefined; - } - >; + ) => Promise<{ + permission: MongoAbility; + role?: { + name: string; + orgId: string; + id: string; + createdAt: Date; + updatedAt: Date; + slug: string; + permissions?: unknown; + description?: string | null | undefined; + }; + }>; getProjectPermissionByRole: ( role: string, projectId: string - ) => Promise< - | { - permission: MongoAbility; - role: { - name: string; - version: number; - id: string; - createdAt: Date; - updatedAt: Date; - projectId: string; - slug: string; - permissions?: unknown; - description?: string | null | undefined; - }; - } - | { - permission: MongoAbility; - role?: undefined; - } - >; - buildOrgPermission: (orgUserRoles: TBuildOrgPermissionDTO) => MongoAbility; - buildProjectPermissionRules: ( - projectUserRoles: TBuildProjectPermissionDTO - ) => RawRuleOf>[]; + ) => Promise<{ + permission: MongoAbility; + role?: { + name: string; + version: number; + id: string; + createdAt: Date; + updatedAt: Date; + projectId: string; + slug: string; + permissions?: unknown; + description?: string | null | undefined; + }; + }>; checkGroupProjectPermission: ({ groupId, projectId, diff --git a/backend/src/ee/services/permission/permission-service.ts b/backend/src/ee/services/permission/permission-service.ts index 9e94e1415..b38c6e1b6 100644 --- a/backend/src/ee/services/permission/permission-service.ts +++ b/backend/src/ee/services/permission/permission-service.ts @@ -1,17 +1,15 @@ import { createMongoAbility, MongoAbility, RawRuleOf } from "@casl/ability"; import { PackRule, unpackRules } from "@casl/ability/extra"; import { requestContext } from "@fastify/request-context"; -import { MongoQuery } from "@ucast/mongo2js"; import handlebars from "handlebars"; import { Knex } from "knex"; import { + AccessScope, ActionProjectType, OrgMembershipRole, ProjectMembershipRole, - ServiceTokenScopes, - TIdentityProjectMemberships, - TProjectMemberships + ServiceTokenScopes } from "@app/db/schemas"; import { cryptographicOperatorPermissions, @@ -25,27 +23,81 @@ import { KeyStorePrefixes, KeyStoreTtls, TKeyStoreFactory } from "@app/keystore/ import { conditionsMatcher } from "@app/lib/casl"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { objectify } from "@app/lib/fn"; -import { logger } from "@app/lib/logger"; import { ActorType } from "@app/services/auth/auth-type"; +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 { TProjectRoleDALFactory } from "@app/services/project-role/project-role-dal"; import { TServiceTokenDALFactory } from "@app/services/service-token/service-token-dal"; +import { TUserDALFactory } from "@app/services/user/user-dal"; import { orgAdminPermissions, orgMemberPermissions, orgNoAccessPermissions, OrgPermissionSet } from "./org-permission"; import { TPermissionDALFactory } from "./permission-dal"; -import { escapeHandlebarsMissingDict, validateOrgSSO } from "./permission-fns"; +import { escapeHandlebarsMissingDict } from "./permission-fns"; import { TBuildOrgPermissionDTO, TBuildProjectPermissionDTO, - TGetIdentityProjectPermissionArg, - TGetProjectPermissionArg, TGetServiceTokenProjectPermissionArg, - TGetUserProjectPermissionArg, TPermissionServiceFactory } from "./permission-service-types"; import { buildServiceTokenProjectPermission, ProjectPermissionSet } from "./project-permission"; +const buildOrgPermissionRules = (orgUserRoles: TBuildOrgPermissionDTO) => { + const rules = orgUserRoles + .map(({ role, permissions }) => { + switch (role) { + case OrgMembershipRole.Admin: + return orgAdminPermissions; + case OrgMembershipRole.Member: + return orgMemberPermissions; + case OrgMembershipRole.NoAccess: + return orgNoAccessPermissions; + case OrgMembershipRole.Custom: + return unpackRules>>( + permissions as PackRule>>[] + ); + default: + throw new NotFoundError({ name: "OrgRoleInvalid", message: `Organization role '${role}' not found` }); + } + }) + .reduce((prev, curr) => prev.concat(curr), []); + + return rules; +}; + +const buildProjectPermissionRules = (projectUserRoles: TBuildProjectPermissionDTO) => { + const rules = projectUserRoles + .map(({ role, permissions }) => { + switch (role) { + case ProjectMembershipRole.Admin: + return projectAdminPermissions; + case ProjectMembershipRole.Member: + return projectMemberPermissions; + case ProjectMembershipRole.Viewer: + return projectViewerPermission; + case ProjectMembershipRole.NoAccess: + return projectNoAccessPermissions; + case ProjectMembershipRole.SshHostBootstrapper: + return sshHostBootstrapPermissions; + case ProjectMembershipRole.KmsCryptographicOperator: + return cryptographicOperatorPermissions; + case ProjectMembershipRole.Custom: { + return unpackRules>>( + permissions as PackRule>>[] + ); + } + default: + throw new NotFoundError({ + name: "ProjectRoleInvalid", + message: `Project role '${role}' not found` + }); + } + }) + .reduce((prev, curr) => prev.concat(curr), []); + + return rules; +}; + type TPermissionServiceFactoryDep = { orgRoleDAL: Pick; projectRoleDAL: Pick; @@ -53,6 +105,8 @@ type TPermissionServiceFactoryDep = { projectDAL: Pick; permissionDAL: TPermissionDALFactory; keyStore: TKeyStoreFactory; + userDAL: Pick; + identityDAL: Pick; }; export const permissionServiceFactory = ({ @@ -61,33 +115,10 @@ export const permissionServiceFactory = ({ projectRoleDAL, serviceTokenDAL, projectDAL, + userDAL, + identityDAL, keyStore }: TPermissionServiceFactoryDep): TPermissionServiceFactory => { - const buildOrgPermission = (orgUserRoles: TBuildOrgPermissionDTO) => { - const rules = orgUserRoles - .map(({ role, permissions }) => { - switch (role) { - case OrgMembershipRole.Admin: - return orgAdminPermissions; - case OrgMembershipRole.Member: - return orgMemberPermissions; - case OrgMembershipRole.NoAccess: - return orgNoAccessPermissions; - case OrgMembershipRole.Custom: - return unpackRules>>( - permissions as PackRule>>[] - ); - default: - throw new NotFoundError({ name: "OrgRoleInvalid", message: `Organization role '${role}' not found` }); - } - }) - .reduce((prev, curr) => prev.concat(curr), []); - - return createMongoAbility(rules, { - conditionsMatcher - }); - }; - const invalidateProjectPermissionCache = async (projectId: string, tx?: Knex) => { const projectPermissionDalVersionKey = KeyStorePrefixes.ProjectPermissionDalVersion(projectId); await keyStore.pgIncrementBy(projectPermissionDalVersionKey, { @@ -97,147 +128,59 @@ export const permissionServiceFactory = ({ }); }; - const calculateProjectPermissionTtl = (membership: unknown): number => { - const now = new Date(); - let minTtl = KeyStoreTtls.ProjectPermissionCacheInSeconds; - - const getMinEndTime = (items: Array<{ temporaryAccessEndTime?: Date | null; isTemporary?: boolean }>) => { - return items - .filter((item) => item.isTemporary && item.temporaryAccessEndTime) - .map((item) => item.temporaryAccessEndTime!) - .filter((endTime) => endTime > now) - .reduce((min, endTime) => (!min || endTime < min ? endTime : min), null as Date | null); - }; - - const roleTimes: Date[] = []; - const additionalPrivilegeTimes: Date[] = []; - - if ( - membership && - typeof membership === "object" && - "roles" in membership && - Array.isArray((membership as Record).roles) - ) { - const roles = (membership as Record).roles as Array<{ - temporaryAccessEndTime?: Date | null; - isTemporary?: boolean; - }>; - const minRoleEndTime = getMinEndTime(roles); - if (minRoleEndTime) roleTimes.push(minRoleEndTime); - } - - if ( - membership && - typeof membership === "object" && - "additionalPrivileges" in membership && - Array.isArray((membership as Record).additionalPrivileges) - ) { - const additionalPrivileges = (membership as Record).additionalPrivileges as Array<{ - temporaryAccessEndTime?: Date | null; - isTemporary?: boolean; - }>; - const minAdditionalEndTime = getMinEndTime(additionalPrivileges); - if (minAdditionalEndTime) additionalPrivilegeTimes.push(minAdditionalEndTime); - } - - const allEndTimes = [...roleTimes, ...additionalPrivilegeTimes]; - if (allEndTimes.length > 0) { - const nearestEndTime = allEndTimes.reduce((min, endTime) => (!min || endTime < min ? endTime : min)); - const timeUntilExpiry = Math.floor((nearestEndTime.getTime() - now.getTime()) / 1000); - minTtl = Math.min(minTtl, Math.max(1, timeUntilExpiry)); - } - - return minTtl; - }; - - const buildProjectPermissionRules = (projectUserRoles: TBuildProjectPermissionDTO) => { - const rules = projectUserRoles - .map(({ role, permissions }) => { - switch (role) { - case ProjectMembershipRole.Admin: - return projectAdminPermissions; - case ProjectMembershipRole.Member: - return projectMemberPermissions; - case ProjectMembershipRole.Viewer: - return projectViewerPermission; - case ProjectMembershipRole.NoAccess: - return projectNoAccessPermissions; - case ProjectMembershipRole.SshHostBootstrapper: - return sshHostBootstrapPermissions; - case ProjectMembershipRole.KmsCryptographicOperator: - return cryptographicOperatorPermissions; - case ProjectMembershipRole.Custom: { - return unpackRules>>( - permissions as PackRule>>[] - ); - } - default: - throw new NotFoundError({ - name: "ProjectRoleInvalid", - message: `Project role '${role}' not found` - }); - } - }) - .reduce((prev, curr) => prev.concat(curr), []); - - return rules; - }; - - /* - * Get user permission in an organization - */ - const getUserOrgPermission: TPermissionServiceFactory["getUserOrgPermission"] = async ( - userId, - orgId, - authMethod, - userOrgId - ) => { - // when token is scoped, ensure the passed org id is same as user org id - if (userOrgId && userOrgId !== orgId) - throw new ForbiddenRequestError({ message: "Invalid user token. Scoped to different organization." }); - const membership = await permissionDAL.getOrgPermission(userId, orgId); - if (!membership) throw new ForbiddenRequestError({ name: "You are not apart of this organization" }); - if (membership.role === OrgMembershipRole.Custom && !membership.permissions) { - throw new BadRequestError({ name: "Custom organization permission not found" }); - } - - // If the org ID is API_KEY, the request is being made with an API Key. - // Since we can't scope API keys to an organization, we'll need to do an arbitrary check to see if the user is a member of the organization. - - // Extra: This means that when users are using API keys to make requests, they can't use slug-based routes. - // Slug-based routes depend on the organization ID being present on the request, since project slugs aren't globally unique, and we need a way to filter by organization. - if (userOrgId !== "API_KEY" && membership.orgId !== userOrgId) { - throw new ForbiddenRequestError({ name: "You are not logged into this organization" }); - } - - validateOrgSSO( - authMethod, - membership.orgAuthEnforced, - membership.orgGoogleSsoAuthEnforced, - membership.bypassOrgAuthEnabled, - membership.role as OrgMembershipRole - ); - - const finalPolicyRoles = [{ role: membership.role, permissions: membership.permissions }].concat( - membership?.groups?.map(({ role, customRolePermission }) => ({ - role, - permissions: customRolePermission - })) || [] - ); - return { permission: buildOrgPermission(finalPolicyRoles), membership }; - }; - - const getIdentityOrgPermission = async (identityId: string, orgId: string) => { - const membership = await permissionDAL.getOrgIdentityPermission(identityId, orgId); - if (!membership) throw new ForbiddenRequestError({ name: "Identity is not apart of this organization" }); - if (membership.role === OrgMembershipRole.Custom && !membership.permissions) { - throw new NotFoundError({ name: `Custom organization permission not found for identity ${identityId}` }); - } - return { - permission: buildOrgPermission([{ role: membership.role, permissions: membership.permissions }]), - membership - }; - }; + // akhilmdhh: will bring this up later + // const calculateProjectPermissionTtl = (membership: unknown): number => { + // const now = new Date(); + // let minTtl = KeyStoreTtls.ProjectPermissionCacheInSeconds; + // + // const getMinEndTime = (items: Array<{ temporaryAccessEndTime?: Date | null; isTemporary?: boolean }>) => { + // return items + // .filter((item) => item.isTemporary && item.temporaryAccessEndTime) + // .map((item) => item.temporaryAccessEndTime!) + // .filter((endTime) => endTime > now) + // .reduce((min, endTime) => (!min || endTime < min ? endTime : min), null as Date | null); + // }; + // + // const roleTimes: Date[] = []; + // const additionalPrivilegeTimes: Date[] = []; + // + // if ( + // membership && + // typeof membership === "object" && + // "roles" in membership && + // Array.isArray((membership as Record).roles) + // ) { + // const roles = (membership as Record).roles as Array<{ + // temporaryAccessEndTime?: Date | null; + // isTemporary?: boolean; + // }>; + // const minRoleEndTime = getMinEndTime(roles); + // if (minRoleEndTime) roleTimes.push(minRoleEndTime); + // } + // + // if ( + // membership && + // typeof membership === "object" && + // "additionalPrivileges" in membership && + // Array.isArray((membership as Record).additionalPrivileges) + // ) { + // const additionalPrivileges = (membership as Record).additionalPrivileges as Array<{ + // temporaryAccessEndTime?: Date | null; + // isTemporary?: boolean; + // }>; + // const minAdditionalEndTime = getMinEndTime(additionalPrivileges); + // if (minAdditionalEndTime) additionalPrivilegeTimes.push(minAdditionalEndTime); + // } + // + // const allEndTimes = [...roleTimes, ...additionalPrivilegeTimes]; + // if (allEndTimes.length > 0) { + // const nearestEndTime = allEndTimes.reduce((min, endTime) => (!min || endTime < min ? endTime : min)); + // const timeUntilExpiry = Math.floor((nearestEndTime.getTime() - now.getTime()) / 1000); + // minTtl = Math.min(minTtl, Math.max(1, timeUntilExpiry)); + // } + // + // return minTtl; + // }; const getOrgPermission: TPermissionServiceFactory["getOrgPermission"] = async ( type, @@ -246,17 +189,60 @@ export const permissionServiceFactory = ({ authMethod, actorOrgId ) => { - switch (type) { - case ActorType.USER: - return getUserOrgPermission(id, orgId, authMethod, actorOrgId); - case ActorType.IDENTITY: - return getIdentityOrgPermission(id, orgId); - default: - throw new BadRequestError({ - message: "Invalid actor provided", - name: "Get org permission" - }); + if (type !== ActorType.USER && type !== ActorType.IDENTITY) { + throw new BadRequestError({ + message: "Invalid actor provided", + name: "Get org permission" + }); } + + if (orgId !== actorOrgId) { + throw new ForbiddenRequestError({ name: "You are not logged into this organization" }); + } + + const permissionData = await permissionDAL.getPermission({ + scopeData: { + scope: AccessScope.Organization, + orgId + }, + actorId: id, + actorType: type + }); + if (!permissionData?.length) throw new ForbiddenRequestError({ name: "You are not member of this organization" }); + + const permissionFromRoles = permissionData.flatMap((membership) => { + const activeRoles = membership?.roles + .filter( + ({ isTemporary, temporaryAccessEndTime }) => + !isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime) + ) + .map(({ role, permissions }) => ({ role, permissions })); + const activeAdditionalPrivileges = membership?.additionalPrivileges + .filter( + ({ isTemporary, temporaryAccessEndTime }) => + !isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime) + ) + .map(({ permissions }) => ({ role: OrgMembershipRole.Custom, permissions })); + return activeRoles.concat(activeAdditionalPrivileges); + }); + + const permission = createMongoAbility(buildOrgPermissionRules(permissionFromRoles), { + conditionsMatcher + }); + + // TODO(simp): validate this + // validateOrgSSO( + // authMethod, + // membership.orgAuthEnforced, + // membership.orgGoogleSsoAuthEnforced, + // membership.bypassOrgAuthEnabled, + // membership.role as OrgMembershipRole + // ); + + return { + permission, + memberships: permissionData + }; }; // instead of actor type this will fetch by role slug. meaning it can be the pre defined slugs like @@ -270,175 +256,19 @@ export const permissionServiceFactory = ({ message: `Specified role '${role}' was not found in the organization with ID '${orgId}'` }); return { - permission: buildOrgPermission([{ role: OrgMembershipRole.Custom, permissions: orgRole.permissions }]), + permission: createMongoAbility( + buildOrgPermissionRules([{ role: OrgMembershipRole.Custom, permissions: orgRole.permissions }]), + { + conditionsMatcher + } + ), role: orgRole }; } - return { permission: buildOrgPermission([{ role, permissions: [] }]) }; - }; - - // user permission for a project in an organization - const getUserProjectPermission = async ({ - userId, - projectId, - authMethod, - userOrgId, - actionProjectType - }: TGetUserProjectPermissionArg): Promise> => { - const userProjectPermission = await permissionDAL.getProjectPermission(userId, projectId); - if (!userProjectPermission) throw new ForbiddenRequestError({ name: "User not a part of the specified project" }); - - if ( - userProjectPermission.roles.some(({ role, permissions }) => role === ProjectMembershipRole.Custom && !permissions) - ) { - throw new NotFoundError({ name: "The permission was not found" }); - } - - // If the org ID is API_KEY, the request is being made with an API Key. - // Since we can't scope API keys to an organization, we'll need to do an arbitrary check to see if the user is a member of the organization. - - // Extra: This means that when users are using API keys to make requests, they can't use slug-based routes. - // Slug-based routes depend on the organization ID being present on the request, since project slugs aren't globally unique, and we need a way to filter by organization. - if (userOrgId !== "API_KEY" && userProjectPermission.orgId !== userOrgId) { - throw new ForbiddenRequestError({ name: "You are not logged into this organization" }); - } - - validateOrgSSO( - authMethod, - userProjectPermission.orgAuthEnforced, - userProjectPermission.orgGoogleSsoAuthEnforced, - userProjectPermission.bypassOrgAuthEnabled, - userProjectPermission.orgRole - ); - - if (actionProjectType !== ActionProjectType.Any && actionProjectType !== userProjectPermission.projectType) { - throw new BadRequestError({ - message: `The project is of type ${userProjectPermission.projectType}. Operations of type ${actionProjectType} are not allowed.` - }); - } - - // join two permissions and pass to build the final permission set - const rolePermissions = userProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || []; - const additionalPrivileges = - userProjectPermission.additionalPrivileges?.map(({ permissions }) => ({ - role: ProjectMembershipRole.Custom, - permissions - })) || []; - - const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges)); - const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false }); - const unescapedMetadata = objectify( - userProjectPermission.metadata, - (i) => i.key, - (i) => i.value - ); - const metadataKeyValuePair = escapeHandlebarsMissingDict(unescapedMetadata, "identity.metadata"); - requestContext.set("identityPermissionMetadata", { metadata: unescapedMetadata }); - const interpolateRules = templatedRules( - { - identity: { - id: userProjectPermission.userId, - username: userProjectPermission.username, - metadata: metadataKeyValuePair - } - }, - { data: false } - ); - const permission = createMongoAbility( - JSON.parse(interpolateRules) as RawRuleOf>[], - { - conditionsMatcher - } - ); - return { - permission, - membership: userProjectPermission, - hasRole: (role: string) => - userProjectPermission.roles.findIndex( - ({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug - ) !== -1 - }; - }; - - const getIdentityProjectPermission = async ({ - identityId, - projectId, - identityOrgId, - actionProjectType - }: TGetIdentityProjectPermissionArg): Promise> => { - const identityProjectPermission = await permissionDAL.getProjectIdentityPermission(identityId, projectId); - if (!identityProjectPermission) - throw new ForbiddenRequestError({ - name: `Identity is not a member of the specified project with ID '${projectId}'` - }); - - if ( - identityProjectPermission.roles.some( - ({ role, permissions }) => role === ProjectMembershipRole.Custom && !permissions - ) - ) { - throw new NotFoundError({ name: "Custom permission not found" }); - } - - if (identityProjectPermission.orgId !== identityOrgId) { - throw new ForbiddenRequestError({ name: "Identity is not a member of the specified organization" }); - } - - if (actionProjectType !== ActionProjectType.Any && actionProjectType !== identityProjectPermission.projectType) { - throw new BadRequestError({ - message: `The project is of type ${identityProjectPermission.projectType}. Operations of type ${actionProjectType} are not allowed.` - }); - } - - const rolePermissions = - identityProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || []; - const additionalPrivileges = - identityProjectPermission.additionalPrivileges?.map(({ permissions }) => ({ - role: ProjectMembershipRole.Custom, - permissions - })) || []; - - const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges)); - const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false }); - const unescapedIdentityAuthInfo = requestContext.get("identityAuthInfo"); - const unescapedMetadata = objectify( - identityProjectPermission.metadata, - (i) => i.key, - (i) => i.value - ); - const identityAuthInfo = - unescapedIdentityAuthInfo?.identityId === identityId && unescapedIdentityAuthInfo - ? escapeHandlebarsMissingDict(unescapedIdentityAuthInfo as never, "identity.auth") - : {}; - const metadataKeyValuePair = escapeHandlebarsMissingDict(unescapedMetadata, "identity.metadata"); - - requestContext.set("identityPermissionMetadata", { metadata: unescapedMetadata, auth: unescapedIdentityAuthInfo }); - const interpolateRules = templatedRules( - { - identity: { - id: identityProjectPermission.identityId, - username: identityProjectPermission.username, - metadata: metadataKeyValuePair, - auth: identityAuthInfo - } - }, - { data: false } - ); - const permission = createMongoAbility( - JSON.parse(interpolateRules) as RawRuleOf>[], - { + permission: createMongoAbility(buildOrgPermissionRules([{ role, permissions: [] }]), { conditionsMatcher - } - ); - - return { - permission, - membership: identityProjectPermission, - hasRole: (role: string) => - identityProjectPermission.roles.findIndex( - ({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug - ) !== -1 + }) }; }; @@ -480,30 +310,149 @@ export const permissionServiceFactory = ({ const scopes = ServiceTokenScopes.parse(serviceToken.scopes || []); return { permission: buildServiceTokenProjectPermission(scopes, serviceToken.permissions), - membership: { - shouldUseNewPrivilegeSystem: true - } + memberships: [], + hasRole: () => false }; }; - type TProjectPermissionRT = T extends ActorType.SERVICE - ? { - permission: MongoAbility; - membership: { - shouldUseNewPrivilegeSystem: boolean; - }; - hasRole: (arg: string) => boolean; - } // service token doesn't have both membership and roles - : { - permission: MongoAbility; - membership: (T extends ActorType.USER ? TProjectMemberships : TIdentityProjectMemberships) & { - orgAuthEnforced: boolean | null | undefined; - orgId: string; - roles: Array<{ role: string }>; - shouldUseNewPrivilegeSystem: boolean; - }; - hasRole: (role: string) => boolean; - }; + const getProjectPermission: TPermissionServiceFactory["getProjectPermission"] = async ({ + actor: inputActor, + actorId: inputActorId, + projectId, + actorAuthMethod, + actorOrgId, + actionProjectType + }) => { + let actor = inputActor; + let actorId = inputActorId; + + if (actor === ActorType.SERVICE) { + return getServiceTokenProjectPermission({ + serviceTokenId: actorId, + projectId, + actorOrgId, + actionProjectType + }); + } + + const assumedPrivilegeDetailsCtx = requestContext.get("assumedPrivilegeDetails"); + if ( + assumedPrivilegeDetailsCtx && + actor === ActorType.USER && + actorId === assumedPrivilegeDetailsCtx.requesterId && + projectId === assumedPrivilegeDetailsCtx.projectId + ) { + actor = assumedPrivilegeDetailsCtx.actorType; + actorId = assumedPrivilegeDetailsCtx.actorId; + } + + if (ActorType.USER !== actor && ActorType.IDENTITY !== actor) { + throw new BadRequestError({ + message: "Invalid actor provided", + name: "Get org permission" + }); + } + + const projectDetails = await projectDAL.findById(projectId); + if (!projectDetails) { + throw new NotFoundError({ message: `Project with ${projectId} not found` }); + } + + if (projectDetails.orgId !== actorOrgId) { + throw new ForbiddenRequestError({ name: "You are not logged into this organization" }); + } + + if (actionProjectType !== ActionProjectType.Any && actionProjectType !== projectDetails.type) { + throw new BadRequestError({ + message: `The project is of type ${projectDetails.type}. Operations of type ${actionProjectType} are not allowed.` + }); + } + + const permissionData = await permissionDAL.getPermission({ + scopeData: { + scope: AccessScope.Project, + orgId: projectDetails.orgId, + projectId + }, + actorId, + actorType: actor + }); + if (!permissionData?.length) throw new ForbiddenRequestError({ name: "You are not member of this organization" }); + const permissionFromRoles = permissionData.flatMap((membership) => { + const activeRoles = membership?.roles + .filter( + ({ isTemporary, temporaryAccessEndTime }) => + !isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime) + ) + .map(({ role, permissions }) => ({ role, permissions })); + const activeAdditionalPrivileges = membership?.additionalPrivileges + .filter( + ({ isTemporary, temporaryAccessEndTime }) => + !isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime) + ) + .map(({ permissions }) => ({ role: ProjectMembershipRole.Custom, permissions })); + return activeRoles.concat(activeAdditionalPrivileges); + }); + + // validateOrgSSO( + // authMethod, + // userProjectPermission.orgAuthEnforced, + // userProjectPermission.orgGoogleSsoAuthEnforced, + // userProjectPermission.bypassOrgAuthEnabled, + // userProjectPermission.orgRole + // ); + + const rules = buildProjectPermissionRules(permissionFromRoles); + const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false }); + const unescapedMetadata = objectify( + permissionData?.[0]?.metadata, + (i) => i.key, + (i) => i.value + ); + const metadataKeyValuePair = escapeHandlebarsMissingDict(unescapedMetadata, "identity.metadata"); + requestContext.set("identityPermissionMetadata", { metadata: unescapedMetadata }); + + let username = ""; + if (actor === ActorType.USER) { + const userDetails = await userDAL.findById(actorId); + username = userDetails?.username; + } else { + const identityDetails = await identityDAL.findById(actorId); + username = identityDetails?.name; + } + + const unescapedIdentityAuthInfo = requestContext.get("identityAuthInfo"); + const identityAuthInfo = + unescapedIdentityAuthInfo?.identityId === actorId && unescapedIdentityAuthInfo + ? escapeHandlebarsMissingDict(unescapedIdentityAuthInfo as never, "identity.auth") + : {}; + + const interpolateRules = templatedRules( + { + identity: { + id: actorId, + username, + metadata: metadataKeyValuePair, + auth: identityAuthInfo + } + }, + { data: false } + ); + + const permission = createMongoAbility( + JSON.parse(interpolateRules) as RawRuleOf>[], + { + conditionsMatcher + } + ); + + return { + permission, + memberships: permissionData, + hasRole: (role: string) => + permissionData.some((memberships) => memberships.roles.some((el) => role === (el.customRoleSlug || el.role))) + }; + }; const getProjectPermissions: TPermissionServiceFactory["getProjectPermissions"] = async (projectId) => { // fetch user permissions @@ -623,117 +572,6 @@ export const permissionServiceFactory = ({ }; }; - const getProjectPermission = async ({ - actor: inputActor, - actorId: inputActorId, - projectId, - actorAuthMethod, - actorOrgId, - actionProjectType - }: TGetProjectPermissionArg): Promise> => { - let actor = inputActor; - let actorId = inputActorId; - const assumedPrivilegeDetailsCtx = requestContext.get("assumedPrivilegeDetails"); - if ( - assumedPrivilegeDetailsCtx && - actor === ActorType.USER && - actorId === assumedPrivilegeDetailsCtx.requesterId && - projectId === assumedPrivilegeDetailsCtx.projectId - ) { - actor = assumedPrivilegeDetailsCtx.actorType; - actorId = assumedPrivilegeDetailsCtx.actorId; - } - - if (actor === ActorType.SERVICE) { - return getServiceTokenProjectPermission({ - serviceTokenId: actorId, - projectId, - actorOrgId, - actionProjectType - }) as Promise>; - } - - const cachedProjectPermissionVersion = await keyStore.pgGetIntItem( - KeyStorePrefixes.ProjectPermissionDalVersion(projectId) - ); - const projectPermissionVersion = Number(cachedProjectPermissionVersion || 0); - const cacheKey = KeyStorePrefixes.ProjectPermission( - projectId, - projectPermissionVersion, - actor, - actorId, - actionProjectType || ActionProjectType.Any - ); - - try { - const cachedData = await keyStore.getItem(cacheKey); - if (cachedData) { - const parsed = JSON.parse(cachedData) as { - rules: RawRuleOf>[]; - membership: { - roles?: Array<{ role: string; customRoleSlug?: string }>; - [key: string]: unknown; - }; - }; - const permission = createMongoAbility(parsed.rules, { - conditionsMatcher - }); - - return { - permission, - membership: parsed.membership, - hasRole: (role: string) => - parsed.membership.roles?.findIndex( - ({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug - ) !== -1 - } as TProjectPermissionRT; - } - } catch (error) { - logger.error(error, "Failed to get project permission"); - } - - let result: TProjectPermissionRT; - - switch (actor) { - case ActorType.USER: - result = (await getUserProjectPermission({ - userId: actorId, - projectId, - authMethod: actorAuthMethod, - userOrgId: actorOrgId, - actionProjectType - })) as TProjectPermissionRT; - break; - case ActorType.IDENTITY: - result = (await getIdentityProjectPermission({ - identityId: actorId, - projectId, - identityOrgId: actorOrgId, - actionProjectType - })) as TProjectPermissionRT; - break; - default: - throw new BadRequestError({ - message: "Invalid actor provided", - name: "Get project permission" - }); - } - - try { - const cacheData = { - rules: result.permission.rules, - membership: result.membership - }; - - const ttl = calculateProjectPermissionTtl(result.membership); - await keyStore.setItemWithExpiry(cacheKey, ttl, JSON.stringify(cacheData)); - } catch (error) { - logger.error(error, "Failed to cache project permission"); - } - - return result; - }; - const getProjectPermissionByRole: TPermissionServiceFactory["getProjectPermissionByRole"] = async ( role, projectId @@ -785,15 +623,11 @@ export const permissionServiceFactory = ({ }; return { - getUserOrgPermission, getOrgPermission, - getUserProjectPermission, getProjectPermission, getProjectPermissions, getOrgPermissionByRole, getProjectPermissionByRole, - buildOrgPermission, - buildProjectPermissionRules, checkGroupProjectPermission, invalidateProjectPermissionCache };