mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
feat:simplified permission service
This commit is contained in:
4
backend/src/@types/knex.d.ts
vendored
4
backend/src/@types/knex.d.ts
vendored
@@ -1334,12 +1334,12 @@ declare module "knex/types/tables" {
|
||||
|
||||
[TableName.Namespace]: KnexOriginal.CompositeTableType<TNamespaces, TNamespacesInsert, TNamespacesUpdate>;
|
||||
[TableName.Membership]: KnexOriginal.CompositeTableType<TMemberships, TMembershipsInsert, TMembershipsUpdate>;
|
||||
[TableName.MembershipRoles]: KnexOriginal.CompositeTableType<
|
||||
[TableName.MembershipRole]: KnexOriginal.CompositeTableType<
|
||||
TMembershipRoles,
|
||||
TMembershipRolesInsert,
|
||||
TMembershipRolesUpdate
|
||||
>;
|
||||
[TableName.Roles]: KnexOriginal.CompositeTableType<TRoles, TRolesInsert, TRolesUpdate>;
|
||||
[TableName.Role]: KnexOriginal.CompositeTableType<TRoles, TRolesInsert, TRolesUpdate>;
|
||||
[TableName.AdditionalPrivilege]: KnexOriginal.CompositeTableType<
|
||||
TAdditionalPrivileges,
|
||||
TAdditionalPrivilegesInsert,
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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<TPermissionDataReturn[]>;
|
||||
}
|
||||
|
||||
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<Awaited<typeof groupSubQuery>[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<TProjectRoles>(
|
||||
{ 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<TProjects>(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
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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<OrgPermissionSet, MongoQuery>;
|
||||
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<OrgPermissionSet, MongoQuery>;
|
||||
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<OrgPermissionSet, MongoQuery>;
|
||||
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<OrgPermissionSet, MongoQuery>;
|
||||
memberships: TMemberships[];
|
||||
}>;
|
||||
getProjectPermission: (arg: TGetProjectPermissionArg) => Promise<{
|
||||
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
||||
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: <T extends ActorType>(
|
||||
arg: TGetProjectPermissionArg
|
||||
) => Promise<
|
||||
T extends ActorType.SERVICE
|
||||
? {
|
||||
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
||||
membership: {
|
||||
shouldUseNewPrivilegeSystem: boolean;
|
||||
};
|
||||
hasRole: (arg: string) => boolean;
|
||||
}
|
||||
: {
|
||||
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
||||
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<ProjectPermissionSet, MongoQuery>;
|
||||
@@ -226,55 +86,40 @@ export type TPermissionServiceFactory = {
|
||||
membershipId: string;
|
||||
}[];
|
||||
}>;
|
||||
// TODO(simp): switch to role dal later
|
||||
getOrgPermissionByRole: (
|
||||
role: string,
|
||||
orgId: string
|
||||
) => Promise<
|
||||
| {
|
||||
permission: MongoAbility<OrgPermissionSet, MongoQuery>;
|
||||
role: {
|
||||
name: string;
|
||||
orgId: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
slug: string;
|
||||
permissions?: unknown;
|
||||
description?: string | null | undefined;
|
||||
};
|
||||
}
|
||||
| {
|
||||
permission: MongoAbility<OrgPermissionSet, MongoQuery>;
|
||||
role?: undefined;
|
||||
}
|
||||
>;
|
||||
) => Promise<{
|
||||
permission: MongoAbility<OrgPermissionSet, MongoQuery>;
|
||||
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<ProjectPermissionSet, MongoQuery>;
|
||||
role: {
|
||||
name: string;
|
||||
version: number;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
projectId: string;
|
||||
slug: string;
|
||||
permissions?: unknown;
|
||||
description?: string | null | undefined;
|
||||
};
|
||||
}
|
||||
| {
|
||||
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
||||
role?: undefined;
|
||||
}
|
||||
>;
|
||||
buildOrgPermission: (orgUserRoles: TBuildOrgPermissionDTO) => MongoAbility<OrgPermissionSet, MongoQuery>;
|
||||
buildProjectPermissionRules: (
|
||||
projectUserRoles: TBuildProjectPermissionDTO
|
||||
) => RawRuleOf<MongoAbility<ProjectPermissionSet>>[];
|
||||
) => Promise<{
|
||||
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
||||
role?: {
|
||||
name: string;
|
||||
version: number;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
projectId: string;
|
||||
slug: string;
|
||||
permissions?: unknown;
|
||||
description?: string | null | undefined;
|
||||
};
|
||||
}>;
|
||||
checkGroupProjectPermission: ({
|
||||
groupId,
|
||||
projectId,
|
||||
|
||||
@@ -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<RawRuleOf<MongoAbility<OrgPermissionSet>>>(
|
||||
permissions as PackRule<RawRuleOf<MongoAbility<OrgPermissionSet>>>[]
|
||||
);
|
||||
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<RawRuleOf<MongoAbility<ProjectPermissionSet>>>(
|
||||
permissions as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[]
|
||||
);
|
||||
}
|
||||
default:
|
||||
throw new NotFoundError({
|
||||
name: "ProjectRoleInvalid",
|
||||
message: `Project role '${role}' not found`
|
||||
});
|
||||
}
|
||||
})
|
||||
.reduce((prev, curr) => prev.concat(curr), []);
|
||||
|
||||
return rules;
|
||||
};
|
||||
|
||||
type TPermissionServiceFactoryDep = {
|
||||
orgRoleDAL: Pick<TOrgRoleDALFactory, "findOne">;
|
||||
projectRoleDAL: Pick<TProjectRoleDALFactory, "findOne">;
|
||||
@@ -53,6 +105,8 @@ type TPermissionServiceFactoryDep = {
|
||||
projectDAL: Pick<TProjectDALFactory, "findById">;
|
||||
permissionDAL: TPermissionDALFactory;
|
||||
keyStore: TKeyStoreFactory;
|
||||
userDAL: Pick<TUserDALFactory, "findById">;
|
||||
identityDAL: Pick<TIdentityDALFactory, "findById">;
|
||||
};
|
||||
|
||||
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<RawRuleOf<MongoAbility<OrgPermissionSet>>>(
|
||||
permissions as PackRule<RawRuleOf<MongoAbility<OrgPermissionSet>>>[]
|
||||
);
|
||||
default:
|
||||
throw new NotFoundError({ name: "OrgRoleInvalid", message: `Organization role '${role}' not found` });
|
||||
}
|
||||
})
|
||||
.reduce((prev, curr) => prev.concat(curr), []);
|
||||
|
||||
return createMongoAbility<OrgPermissionSet>(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<string, unknown>).roles)
|
||||
) {
|
||||
const roles = (membership as Record<string, unknown>).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<string, unknown>).additionalPrivileges)
|
||||
) {
|
||||
const additionalPrivileges = (membership as Record<string, unknown>).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<RawRuleOf<MongoAbility<ProjectPermissionSet>>>(
|
||||
permissions as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[]
|
||||
);
|
||||
}
|
||||
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<string, unknown>).roles)
|
||||
// ) {
|
||||
// const roles = (membership as Record<string, unknown>).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<string, unknown>).additionalPrivileges)
|
||||
// ) {
|
||||
// const additionalPrivileges = (membership as Record<string, unknown>).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<OrgPermissionSet>(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<OrgPermissionSet>(
|
||||
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<TProjectPermissionRT<ActorType.USER>> => {
|
||||
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<ProjectPermissionSet>(
|
||||
JSON.parse(interpolateRules) as RawRuleOf<MongoAbility<ProjectPermissionSet>>[],
|
||||
{
|
||||
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<TProjectPermissionRT<ActorType.IDENTITY>> => {
|
||||
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<ProjectPermissionSet>(
|
||||
JSON.parse(interpolateRules) as RawRuleOf<MongoAbility<ProjectPermissionSet>>[],
|
||||
{
|
||||
permission: createMongoAbility<OrgPermissionSet>(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> = T extends ActorType.SERVICE
|
||||
? {
|
||||
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
||||
membership: {
|
||||
shouldUseNewPrivilegeSystem: boolean;
|
||||
};
|
||||
hasRole: (arg: string) => boolean;
|
||||
} // service token doesn't have both membership and roles
|
||||
: {
|
||||
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
||||
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<ProjectPermissionSet>(
|
||||
JSON.parse(interpolateRules) as RawRuleOf<MongoAbility<ProjectPermissionSet>>[],
|
||||
{
|
||||
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 <T extends ActorType>({
|
||||
actor: inputActor,
|
||||
actorId: inputActorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType
|
||||
}: TGetProjectPermissionArg): Promise<TProjectPermissionRT<T>> => {
|
||||
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<TProjectPermissionRT<T>>;
|
||||
}
|
||||
|
||||
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<MongoAbility<ProjectPermissionSet>>[];
|
||||
membership: {
|
||||
roles?: Array<{ role: string; customRoleSlug?: string }>;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
const permission = createMongoAbility<ProjectPermissionSet>(parsed.rules, {
|
||||
conditionsMatcher
|
||||
});
|
||||
|
||||
return {
|
||||
permission,
|
||||
membership: parsed.membership,
|
||||
hasRole: (role: string) =>
|
||||
parsed.membership.roles?.findIndex(
|
||||
({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug
|
||||
) !== -1
|
||||
} as TProjectPermissionRT<T>;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(error, "Failed to get project permission");
|
||||
}
|
||||
|
||||
let result: TProjectPermissionRT<T>;
|
||||
|
||||
switch (actor) {
|
||||
case ActorType.USER:
|
||||
result = (await getUserProjectPermission({
|
||||
userId: actorId,
|
||||
projectId,
|
||||
authMethod: actorAuthMethod,
|
||||
userOrgId: actorOrgId,
|
||||
actionProjectType
|
||||
})) as TProjectPermissionRT<T>;
|
||||
break;
|
||||
case ActorType.IDENTITY:
|
||||
result = (await getIdentityProjectPermission({
|
||||
identityId: actorId,
|
||||
projectId,
|
||||
identityOrgId: actorOrgId,
|
||||
actionProjectType
|
||||
})) as TProjectPermissionRT<T>;
|
||||
break;
|
||||
default:
|
||||
throw new BadRequestError({
|
||||
message: "Invalid actor provided",
|
||||
name: "Get project permission"
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const cacheData = {
|
||||
rules: result.permission.rules,
|
||||
membership: result.membership
|
||||
};
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user