mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
feat: completed additional privilege factory
This commit is contained in:
@@ -310,6 +310,16 @@ export enum ActionProjectType {
|
||||
Any = "any"
|
||||
}
|
||||
|
||||
export enum TemporaryPermissionMode {
|
||||
Relative = "relative"
|
||||
}
|
||||
|
||||
export enum MembershipActors {
|
||||
Group = "group",
|
||||
User = "user",
|
||||
Identity = "identity"
|
||||
}
|
||||
|
||||
export enum SortDirection {
|
||||
ASC = "asc",
|
||||
DESC = "desc"
|
||||
|
||||
@@ -292,6 +292,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
|
||||
|
||||
// TODO(simp): look into this in the new ap service
|
||||
await accessApprovalRequestDAL.update(
|
||||
{
|
||||
privilegeId: userPrivilege.id
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
|
||||
export type TAdditionalPrivilegeDALFactory = ReturnType<typeof additionalPrivilegeDALFactory>;
|
||||
|
||||
export const additionalPrivilegeDALFactory = (db: TDbClient) => {
|
||||
const orm = ormify(db, TableName.AdditionalPrivilege);
|
||||
return orm;
|
||||
};
|
||||
@@ -0,0 +1,208 @@
|
||||
import { RawRule } from "@casl/ability";
|
||||
import { packRules } from "@casl/ability/extra";
|
||||
|
||||
import { AccessScope, TemporaryPermissionMode } from "@app/db/schemas";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { ms } from "@app/lib/ms";
|
||||
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
|
||||
import { unpackPermissions } from "@app/server/routes/sanitizedSchema/permission";
|
||||
|
||||
import { TMembershipDALFactory } from "../membership/membership-dal";
|
||||
import { TOrgDALFactory } from "../org/org-dal";
|
||||
import { TAdditionalPrivilegeDALFactory } from "./additional-privilege-dal";
|
||||
import {
|
||||
TAdditionalPrivilegesScopeFactory,
|
||||
TCreateAdditionalPrivilegesDTO,
|
||||
TDeleteAdditionalPrivilegesDTO,
|
||||
TGetAdditionalPrivilegesByIdDTO,
|
||||
TListAdditionalPrivilegesDTO,
|
||||
TUpdateAdditionalPrivilegesDTO
|
||||
} from "./additional-privilege-types";
|
||||
import { newNamespaceAdditionalPrivilegesFactory } from "./namespace/namespace-additional-privilege-factory";
|
||||
import { newOrgAdditionalPrivilegesFactory } from "./org/org-additional-privilege-factory";
|
||||
import { newProjectAdditionalPrivilegesFactory } from "./project/project-additional-privilege-factory";
|
||||
|
||||
type TAdditionalPrivilegeServiceFactoryDep = {
|
||||
additionalPrivilegeDAL: TAdditionalPrivilegeDALFactory;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
orgDAL: Pick<TOrgDALFactory, "findById">;
|
||||
membershipDAL: Pick<TMembershipDALFactory, "findOne">;
|
||||
};
|
||||
|
||||
export type TAdditionalPrivilegeServiceFactory = ReturnType<typeof additionalPrivilegeServiceFactory>;
|
||||
|
||||
export const additionalPrivilegeServiceFactory = ({
|
||||
additionalPrivilegeDAL,
|
||||
permissionService,
|
||||
orgDAL,
|
||||
membershipDAL
|
||||
}: TAdditionalPrivilegeServiceFactoryDep) => {
|
||||
const scopeFactory: Record<AccessScope, TAdditionalPrivilegesScopeFactory> = {
|
||||
[AccessScope.Organization]: newOrgAdditionalPrivilegesFactory({}),
|
||||
[AccessScope.Project]: newProjectAdditionalPrivilegesFactory({
|
||||
membershipDAL,
|
||||
orgDAL,
|
||||
permissionService
|
||||
}),
|
||||
[AccessScope.Namespace]: newNamespaceAdditionalPrivilegesFactory({})
|
||||
};
|
||||
|
||||
const createAdditionalPrivilege = async (dto: TCreateAdditionalPrivilegesDTO) => {
|
||||
const { scopeData, data } = dto;
|
||||
const factory = scopeFactory[scopeData.scope];
|
||||
const { membershipId } = await factory.onCreateAdditionalPrivilegesGuard(dto);
|
||||
|
||||
const existingSlug = await additionalPrivilegeDAL.findOne({
|
||||
name: data.name,
|
||||
membershipId
|
||||
});
|
||||
if (existingSlug) throw new BadRequestError({ message: `Additional privilege with name ${data.name} exist` });
|
||||
|
||||
validateHandlebarTemplate("Additional Privilege Create", JSON.stringify(data.permissions || []), {
|
||||
allowedExpressions: (val) => val.includes("identity.")
|
||||
});
|
||||
|
||||
if (!data.isTemporary) {
|
||||
const additionalPrivilege = await additionalPrivilegeDAL.create({
|
||||
membershipId,
|
||||
name: data.name,
|
||||
isTemporary: data.isTemporary,
|
||||
permissions: JSON.stringify(packRules(data.permissions as RawRule[]))
|
||||
});
|
||||
|
||||
return { additionalPrivilege: { ...additionalPrivilege, permissions: data.permissions } };
|
||||
}
|
||||
|
||||
if (!data.temporaryAccessStartTime || !data.temporaryRange) {
|
||||
throw new BadRequestError({ message: "Temporary mode expects start time and range" });
|
||||
}
|
||||
|
||||
const relativeTempAllocatedTimeInMs = ms(data.temporaryRange);
|
||||
const additionalPrivilege = await additionalPrivilegeDAL.create({
|
||||
membershipId,
|
||||
name: data.name,
|
||||
isTemporary: data.isTemporary,
|
||||
permissions: data.permissions,
|
||||
temporaryAccessEndTime: new Date(
|
||||
new Date(data.temporaryAccessStartTime).getTime() + relativeTempAllocatedTimeInMs
|
||||
),
|
||||
temporaryAccessStartTime: new Date(data.temporaryAccessStartTime),
|
||||
temporaryMode: TemporaryPermissionMode.Relative,
|
||||
temporaryRange: data.temporaryRange
|
||||
});
|
||||
|
||||
return { additionalPrivilege: { ...additionalPrivilege, permissions: data.permissions } };
|
||||
};
|
||||
|
||||
const updateAdditionalPrivilege = async (dto: TUpdateAdditionalPrivilegesDTO) => {
|
||||
const { scopeData, data } = dto;
|
||||
const factory = scopeFactory[scopeData.scope];
|
||||
const { membershipId } = await factory.onUpdateAdditionalPrivilegesGuard(dto);
|
||||
|
||||
const existingPrivilege = await additionalPrivilegeDAL.findOne({
|
||||
id: dto.selector.id,
|
||||
membershipId
|
||||
});
|
||||
if (!existingPrivilege)
|
||||
throw new NotFoundError({ message: `Additional privilege with name ${data.name} doesn't exist` });
|
||||
|
||||
validateHandlebarTemplate("Additional Privilege Create", JSON.stringify(data.permissions || []), {
|
||||
allowedExpressions: (val) => val.includes("identity.")
|
||||
});
|
||||
|
||||
const updatedData = { ...existingPrivilege, ...data };
|
||||
|
||||
if (!updatedData.isTemporary) {
|
||||
const additionalPrivilege = await additionalPrivilegeDAL.updateById(existingPrivilege.id, {
|
||||
name: updatedData.name,
|
||||
isTemporary: data.isTemporary,
|
||||
permissions: data.permissions ? JSON.stringify(packRules(data.permissions as RawRule[])) : undefined
|
||||
});
|
||||
|
||||
return {
|
||||
additionalPrivilege: { ...additionalPrivilege, permissions: unpackPermissions(additionalPrivilege.permissions) }
|
||||
};
|
||||
}
|
||||
|
||||
if (!updatedData.temporaryAccessStartTime || !updatedData.temporaryRange) {
|
||||
throw new BadRequestError({ message: "Temporary mode expects start time and range" });
|
||||
}
|
||||
|
||||
const relativeTempAllocatedTimeInMs = ms(updatedData.temporaryRange);
|
||||
const additionalPrivilege = await additionalPrivilegeDAL.create({
|
||||
membershipId,
|
||||
name: updatedData.name,
|
||||
isTemporary: updatedData.isTemporary,
|
||||
permissions: updatedData.permissions,
|
||||
temporaryAccessEndTime: new Date(
|
||||
new Date(updatedData.temporaryAccessStartTime).getTime() + relativeTempAllocatedTimeInMs
|
||||
),
|
||||
temporaryAccessStartTime: new Date(updatedData.temporaryAccessStartTime),
|
||||
temporaryMode: TemporaryPermissionMode.Relative,
|
||||
temporaryRange: updatedData.temporaryRange
|
||||
});
|
||||
|
||||
return {
|
||||
additionalPrivilege: { ...additionalPrivilege, permissions: unpackPermissions(additionalPrivilege.permissions) }
|
||||
};
|
||||
};
|
||||
|
||||
const deleteAdditionalPrivilege = async (dto: TDeleteAdditionalPrivilegesDTO) => {
|
||||
const { scopeData, selector } = dto;
|
||||
const factory = scopeFactory[scopeData.scope];
|
||||
const { membershipId } = await factory.onDeleteAdditionalPrivilegesGuard(dto);
|
||||
|
||||
const existingPrivilege = await additionalPrivilegeDAL.findOne({
|
||||
id: selector.id,
|
||||
membershipId
|
||||
});
|
||||
if (!existingPrivilege)
|
||||
throw new NotFoundError({ message: `Additional privilege with id ${selector.id} doesn't exist` });
|
||||
|
||||
const additionalPrivilege = await additionalPrivilegeDAL.deleteById(existingPrivilege.id);
|
||||
return {
|
||||
additionalPrivilege: { ...additionalPrivilege, permissions: unpackPermissions(additionalPrivilege.permissions) }
|
||||
};
|
||||
};
|
||||
|
||||
const getAdditionalPrivilegeById = async (dto: TGetAdditionalPrivilegesByIdDTO) => {
|
||||
const { scopeData, selector } = dto;
|
||||
const factory = scopeFactory[scopeData.scope];
|
||||
const { membershipId } = await factory.onListAdditionalPrivilegesGuard(dto);
|
||||
const additionalPrivilege = await additionalPrivilegeDAL.findOne({
|
||||
id: selector.id,
|
||||
membershipId
|
||||
});
|
||||
if (!additionalPrivilege)
|
||||
throw new NotFoundError({ message: `Additional privilege with id ${selector.id} doesn't exist` });
|
||||
|
||||
return {
|
||||
additionalPrivilege: { ...additionalPrivilege, permissions: unpackPermissions(additionalPrivilege.permissions) }
|
||||
};
|
||||
};
|
||||
|
||||
const listAdditionalPrivileges = async (dto: TListAdditionalPrivilegesDTO) => {
|
||||
const { scopeData } = dto;
|
||||
const factory = scopeFactory[scopeData.scope];
|
||||
const { membershipId } = await factory.onListAdditionalPrivilegesGuard(dto);
|
||||
const additionalPrivileges = await additionalPrivilegeDAL.find({
|
||||
membershipId
|
||||
});
|
||||
|
||||
return {
|
||||
additionalPrivileges: additionalPrivileges.map((el) => ({
|
||||
...el,
|
||||
permissions: unpackPermissions(el.permissions)
|
||||
}))
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
createAdditionalPrivilege,
|
||||
updateAdditionalPrivilege,
|
||||
deleteAdditionalPrivilege,
|
||||
getAdditionalPrivilegeById,
|
||||
listAdditionalPrivileges
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
import { AccessScopeData, MembershipActors, TemporaryPermissionMode } from "@app/db/schemas";
|
||||
import { OrgServiceActor } from "@app/lib/types";
|
||||
|
||||
export interface TAdditionalPrivilegesScopeFactory {
|
||||
onCreateAdditionalPrivilegesGuard: (arg: TCreateAdditionalPrivilegesDTO) => Promise<{ membershipId: string }>;
|
||||
onUpdateAdditionalPrivilegesGuard: (arg: TUpdateAdditionalPrivilegesDTO) => Promise<{ membershipId: string }>;
|
||||
onDeleteAdditionalPrivilegesGuard: (arg: TDeleteAdditionalPrivilegesDTO) => Promise<{ membershipId: string }>;
|
||||
onListAdditionalPrivilegesGuard: (arg: TListAdditionalPrivilegesDTO) => Promise<{ membershipId: string }>;
|
||||
onGetAdditionalPrivilegesByIdGuard: (arg: TGetAdditionalPrivilegesByIdDTO) => Promise<{ membershipId: string }>;
|
||||
getScopeField: (scope: AccessScopeData) => { key: "orgId" | "namespaceId" | "projectId"; value: string };
|
||||
}
|
||||
|
||||
export type TCreateAdditionalPrivilegesDTO = {
|
||||
permission: OrgServiceActor;
|
||||
scopeData: AccessScopeData;
|
||||
data: {
|
||||
actorId: string;
|
||||
actorType: MembershipActors;
|
||||
name: string;
|
||||
permissions: unknown;
|
||||
isTemporary: boolean;
|
||||
temporaryMode?: TemporaryPermissionMode.Relative;
|
||||
temporaryRange?: string;
|
||||
temporaryAccessStartTime?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type TUpdateAdditionalPrivilegesDTO = {
|
||||
permission: OrgServiceActor;
|
||||
scopeData: AccessScopeData;
|
||||
selector: {
|
||||
id: string;
|
||||
actorId: string;
|
||||
actorType: MembershipActors;
|
||||
};
|
||||
data: Partial<{
|
||||
name: string;
|
||||
permissions: unknown;
|
||||
isTemporary: boolean;
|
||||
temporaryMode?: TemporaryPermissionMode.Relative;
|
||||
temporaryRange?: string;
|
||||
temporaryAccessStartTime?: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
export type TListAdditionalPrivilegesDTO = {
|
||||
permission: OrgServiceActor;
|
||||
scopeData: AccessScopeData;
|
||||
selector: {
|
||||
actorId: string;
|
||||
actorType: MembershipActors;
|
||||
};
|
||||
};
|
||||
|
||||
export type TDeleteAdditionalPrivilegesDTO = {
|
||||
permission: OrgServiceActor;
|
||||
scopeData: AccessScopeData;
|
||||
selector: {
|
||||
id: string;
|
||||
actorId: string;
|
||||
actorType: MembershipActors;
|
||||
};
|
||||
};
|
||||
|
||||
export type TGetAdditionalPrivilegesByIdDTO = {
|
||||
permission: OrgServiceActor;
|
||||
scopeData: AccessScopeData;
|
||||
selector: {
|
||||
id: string;
|
||||
actorId: string;
|
||||
actorType: MembershipActors;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
import { AccessScope } from "@app/db/schemas";
|
||||
import { InternalServerError } from "@app/lib/errors";
|
||||
|
||||
import { TAdditionalPrivilegesScopeFactory } from "../additional-privilege-types";
|
||||
|
||||
type TNamespaceAdditionalPrivilegesScopeFactoryDep = Record<string, never>;
|
||||
|
||||
export const newNamespaceAdditionalPrivilegesFactory = (
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
deps: TNamespaceAdditionalPrivilegesScopeFactoryDep
|
||||
): TAdditionalPrivilegesScopeFactory => {
|
||||
const getScopeField: TAdditionalPrivilegesScopeFactory["getScopeField"] = (dto) => {
|
||||
if (dto.scope === AccessScope.Namespace) {
|
||||
return { key: "namespaceId" as const, value: dto.namespaceId };
|
||||
}
|
||||
throw new InternalServerError({ message: "Invalid scope provided for the namespace factory" });
|
||||
};
|
||||
|
||||
const onCreateAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onCreateAdditionalPrivilegesGuard"] =
|
||||
async () => {
|
||||
throw new InternalServerError({ message: "Namespace additional privileges create not implemented" });
|
||||
};
|
||||
|
||||
const onUpdateAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onUpdateAdditionalPrivilegesGuard"] =
|
||||
async () => {
|
||||
throw new InternalServerError({ message: "Namespace additional privileges update not implemented" });
|
||||
};
|
||||
|
||||
const onDeleteAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onDeleteAdditionalPrivilegesGuard"] =
|
||||
async () => {
|
||||
throw new InternalServerError({ message: "Namespace additional privileges delete not implemented" });
|
||||
};
|
||||
|
||||
const onListAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onListAdditionalPrivilegesGuard"] =
|
||||
async () => {
|
||||
throw new InternalServerError({ message: "Namespace additional privileges list not implemented" });
|
||||
};
|
||||
|
||||
const onGetAdditionalPrivilegesByIdGuard: TAdditionalPrivilegesScopeFactory["onGetAdditionalPrivilegesByIdGuard"] =
|
||||
async () => {
|
||||
throw new InternalServerError({ message: "Namespace additional privileges get by id not implemented" });
|
||||
};
|
||||
|
||||
return {
|
||||
onCreateAdditionalPrivilegesGuard,
|
||||
onUpdateAdditionalPrivilegesGuard,
|
||||
onDeleteAdditionalPrivilegesGuard,
|
||||
onListAdditionalPrivilegesGuard,
|
||||
onGetAdditionalPrivilegesByIdGuard,
|
||||
getScopeField
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
import { AccessScope } from "@app/db/schemas";
|
||||
import { InternalServerError } from "@app/lib/errors";
|
||||
|
||||
import { TAdditionalPrivilegesScopeFactory } from "../additional-privilege-types";
|
||||
|
||||
type TOrgAdditionalPrivilegesScopeFactoryDep = Record<string, never>;
|
||||
|
||||
export const newOrgAdditionalPrivilegesFactory = (
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
deps: TOrgAdditionalPrivilegesScopeFactoryDep
|
||||
): TAdditionalPrivilegesScopeFactory => {
|
||||
const getScopeField: TAdditionalPrivilegesScopeFactory["getScopeField"] = (dto) => {
|
||||
if (dto.scope === AccessScope.Organization) {
|
||||
return { key: "orgId" as const, value: dto.orgId };
|
||||
}
|
||||
throw new InternalServerError({ message: "Invalid scope provided for the org factory" });
|
||||
};
|
||||
|
||||
const onCreateAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onCreateAdditionalPrivilegesGuard"] =
|
||||
async () => {
|
||||
throw new InternalServerError({ message: "Org additional privileges create not implemented" });
|
||||
};
|
||||
|
||||
const onUpdateAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onUpdateAdditionalPrivilegesGuard"] =
|
||||
async () => {
|
||||
throw new InternalServerError({ message: "Org additional privileges update not implemented" });
|
||||
};
|
||||
|
||||
const onDeleteAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onDeleteAdditionalPrivilegesGuard"] =
|
||||
async () => {
|
||||
throw new InternalServerError({ message: "Org additional privileges delete not implemented" });
|
||||
};
|
||||
|
||||
const onListAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onListAdditionalPrivilegesGuard"] =
|
||||
async () => {
|
||||
throw new InternalServerError({ message: "Org additional privileges list not implemented" });
|
||||
};
|
||||
|
||||
const onGetAdditionalPrivilegesByIdGuard: TAdditionalPrivilegesScopeFactory["onGetAdditionalPrivilegesByIdGuard"] =
|
||||
async () => {
|
||||
throw new InternalServerError({ message: "Org additional privileges get by id not implemented" });
|
||||
};
|
||||
|
||||
return {
|
||||
onCreateAdditionalPrivilegesGuard,
|
||||
onUpdateAdditionalPrivilegesGuard,
|
||||
onDeleteAdditionalPrivilegesGuard,
|
||||
onListAdditionalPrivilegesGuard,
|
||||
onGetAdditionalPrivilegesByIdGuard,
|
||||
getScopeField
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,222 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
import { AccessScope, ActionProjectType, MembershipActors } from "@app/db/schemas";
|
||||
import {
|
||||
constructPermissionErrorMessage,
|
||||
validatePrivilegeChangeOperation
|
||||
} from "@app/ee/services/permission/permission-fns";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import {
|
||||
ProjectPermissionIdentityActions,
|
||||
ProjectPermissionMemberActions,
|
||||
ProjectPermissionSub
|
||||
} from "@app/ee/services/permission/project-permission";
|
||||
import { BadRequestError, InternalServerError, PermissionBoundaryError } from "@app/lib/errors";
|
||||
import { OrgServiceActor } from "@app/lib/types";
|
||||
import { ActorType } from "@app/services/auth/auth-type";
|
||||
import { TMembershipDALFactory } from "@app/services/membership/membership-dal";
|
||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||
|
||||
import { TAdditionalPrivilegesScopeFactory } from "../additional-privilege-types";
|
||||
|
||||
type TProjectAdditionalPrivilegesScopeFactoryDep = {
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
orgDAL: Pick<TOrgDALFactory, "findById">;
|
||||
membershipDAL: Pick<TMembershipDALFactory, "findOne">;
|
||||
};
|
||||
|
||||
export const newProjectAdditionalPrivilegesFactory = ({
|
||||
permissionService,
|
||||
orgDAL,
|
||||
membershipDAL
|
||||
}: TProjectAdditionalPrivilegesScopeFactoryDep): TAdditionalPrivilegesScopeFactory => {
|
||||
const $getPermission = (permission: OrgServiceActor, projectId: string) => {
|
||||
return permissionService.getProjectPermission({
|
||||
actor: permission.type,
|
||||
actorId: permission.id,
|
||||
actionProjectType: ActionProjectType.Any,
|
||||
actorAuthMethod: permission.authMethod,
|
||||
projectId,
|
||||
actorOrgId: permission.orgId
|
||||
});
|
||||
};
|
||||
|
||||
const $getActorType = (type: MembershipActors) => {
|
||||
if (type === MembershipActors.Group)
|
||||
throw new InternalServerError({ message: "Group additional privilege not implemented for projects" });
|
||||
|
||||
if (type === MembershipActors.Identity) return ActorType.IDENTITY;
|
||||
if (type === MembershipActors.User) return ActorType.USER;
|
||||
|
||||
throw new InternalServerError({ message: `Group additional privilege not implemented for actor: ${String(type)}` });
|
||||
};
|
||||
|
||||
const getScopeField: TAdditionalPrivilegesScopeFactory["getScopeField"] = (dto) => {
|
||||
if (dto.scope === AccessScope.Project) {
|
||||
return { key: "projectId" as const, value: dto.projectId };
|
||||
}
|
||||
throw new BadRequestError({ message: "Invalid scope provided for the factory" });
|
||||
};
|
||||
|
||||
const onCreateAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onCreateAdditionalPrivilegesGuard"] =
|
||||
async (dto) => {
|
||||
const scope = getScopeField(dto.scopeData);
|
||||
const actorType = $getActorType(dto.data.actorType);
|
||||
|
||||
const { permission } = await $getPermission(dto.permission, scope.value);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
|
||||
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(dto.permission.orgId);
|
||||
const { permission: targetUserPermission, memberships } = await $getPermission(
|
||||
{ ...dto.permission, type: actorType, id: dto.data.actorId },
|
||||
scope.value
|
||||
);
|
||||
|
||||
const permissionAction =
|
||||
actorType === ActorType.USER
|
||||
? ProjectPermissionMemberActions.GrantPrivileges
|
||||
: ProjectPermissionIdentityActions.GrantPrivileges;
|
||||
const permissionSubject =
|
||||
actorType === ActorType.USER ? ProjectPermissionSub.Member : ProjectPermissionSub.Identity;
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
permissionAction,
|
||||
permissionSubject,
|
||||
permission,
|
||||
targetUserPermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to update more privileged actor",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
permissionAction,
|
||||
permissionSubject
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
|
||||
const membership = memberships.find(
|
||||
(el) => el[actorType === ActorType.IDENTITY ? "actorIdentityId" : "actorUserId"] === dto.data.actorId
|
||||
);
|
||||
if (!membership) throw new BadRequestError({ message: "Actor doesn't have membership" });
|
||||
return { membershipId: membership.id };
|
||||
};
|
||||
|
||||
const onUpdateAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onUpdateAdditionalPrivilegesGuard"] =
|
||||
async (dto) => {
|
||||
const scope = getScopeField(dto.scopeData);
|
||||
const actorType = $getActorType(dto.selector.actorType);
|
||||
|
||||
const { permission } = await $getPermission(dto.permission, scope.value);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
|
||||
|
||||
const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(dto.permission.orgId);
|
||||
const { permission: targetUserPermission, memberships } = await $getPermission(
|
||||
{ ...dto.permission, type: actorType, id: dto.selector.actorId },
|
||||
scope.value
|
||||
);
|
||||
|
||||
const permissionAction =
|
||||
actorType === ActorType.USER
|
||||
? ProjectPermissionMemberActions.GrantPrivileges
|
||||
: ProjectPermissionIdentityActions.GrantPrivileges;
|
||||
const permissionSubject =
|
||||
actorType === ActorType.USER ? ProjectPermissionSub.Member : ProjectPermissionSub.Identity;
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
shouldUseNewPrivilegeSystem,
|
||||
permissionAction,
|
||||
permissionSubject,
|
||||
permission,
|
||||
targetUserPermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to update more privileged actor",
|
||||
shouldUseNewPrivilegeSystem,
|
||||
permissionAction,
|
||||
permissionSubject
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
|
||||
const membership = memberships.find(
|
||||
(el) => el[actorType === ActorType.IDENTITY ? "actorIdentityId" : "actorUserId"] === dto.selector.actorId
|
||||
);
|
||||
if (!membership) throw new BadRequestError({ message: "Actor doesn't have membership" });
|
||||
return { membershipId: membership.id };
|
||||
};
|
||||
|
||||
const onDeleteAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onDeleteAdditionalPrivilegesGuard"] =
|
||||
async (dto) => {
|
||||
const scope = getScopeField(dto.scopeData);
|
||||
const actorType = $getActorType(dto.selector.actorType);
|
||||
|
||||
const { permission } = await $getPermission(dto.permission, scope.value);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
|
||||
|
||||
const membership = await membershipDAL.findOne({
|
||||
scopeOrgId: dto.permission.orgId,
|
||||
scopeProjectId: scope.value,
|
||||
[actorType === ActorType.USER ? "actorUserId" : "actorIdentityId"]: dto.selector.actorId
|
||||
});
|
||||
|
||||
if (!membership) throw new BadRequestError({ message: "Actor doesn't have membership" });
|
||||
return { membershipId: membership.id };
|
||||
};
|
||||
|
||||
const onListAdditionalPrivilegesGuard: TAdditionalPrivilegesScopeFactory["onListAdditionalPrivilegesGuard"] = async (
|
||||
dto
|
||||
) => {
|
||||
const scope = getScopeField(dto.scopeData);
|
||||
const actorType = $getActorType(dto.selector.actorType);
|
||||
|
||||
const permissionSet =
|
||||
actorType === ActorType.USER
|
||||
? ([ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member] as const)
|
||||
: ([ProjectPermissionIdentityActions.Read, ProjectPermissionSub.Identity] as const);
|
||||
const { permission } = await $getPermission(dto.permission, scope.value);
|
||||
ForbiddenError.from(permission).throwUnlessCan(...permissionSet);
|
||||
|
||||
const membership = await membershipDAL.findOne({
|
||||
scopeOrgId: dto.permission.orgId,
|
||||
scopeProjectId: scope.value,
|
||||
[actorType === ActorType.USER ? "actorUserId" : "actorIdentityId"]: dto.selector.actorId
|
||||
});
|
||||
|
||||
if (!membership) throw new BadRequestError({ message: "Actor doesn't have membership" });
|
||||
return { membershipId: membership.id };
|
||||
};
|
||||
|
||||
const onGetAdditionalPrivilegesByIdGuard: TAdditionalPrivilegesScopeFactory["onGetAdditionalPrivilegesByIdGuard"] =
|
||||
async (dto) => {
|
||||
const scope = getScopeField(dto.scopeData);
|
||||
const actorType = $getActorType(dto.selector.actorType);
|
||||
|
||||
const permissionSet =
|
||||
actorType === ActorType.USER
|
||||
? ([ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member] as const)
|
||||
: ([ProjectPermissionIdentityActions.Read, ProjectPermissionSub.Identity] as const);
|
||||
const { permission } = await $getPermission(dto.permission, scope.value);
|
||||
ForbiddenError.from(permission).throwUnlessCan(...permissionSet);
|
||||
|
||||
const membership = await membershipDAL.findOne({
|
||||
scopeOrgId: dto.permission.orgId,
|
||||
scopeProjectId: scope.value,
|
||||
[actorType === ActorType.USER ? "actorUserId" : "actorIdentityId"]: dto.selector.actorId
|
||||
});
|
||||
|
||||
if (!membership) throw new BadRequestError({ message: "Actor doesn't have membership" });
|
||||
return { membershipId: membership.id };
|
||||
};
|
||||
|
||||
return {
|
||||
onCreateAdditionalPrivilegesGuard,
|
||||
onUpdateAdditionalPrivilegesGuard,
|
||||
onDeleteAdditionalPrivilegesGuard,
|
||||
onListAdditionalPrivilegesGuard,
|
||||
onGetAdditionalPrivilegesByIdGuard,
|
||||
getScopeField
|
||||
};
|
||||
};
|
||||
10
backend/src/services/membership/membership-dal.ts
Normal file
10
backend/src/services/membership/membership-dal.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
|
||||
export type TMembershipDALFactory = ReturnType<typeof membershipDALFactory>;
|
||||
|
||||
export const membershipDALFactory = (db: TDbClient) => {
|
||||
const orm = ormify(db, TableName.Membership);
|
||||
return orm;
|
||||
};
|
||||
11
backend/src/services/membership/membership-service.ts
Normal file
11
backend/src/services/membership/membership-service.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { TMembershipDALFactory } from "./membership-dal";
|
||||
|
||||
type TMembershipServiceFactoryDep = {
|
||||
membershipDAL: TMembershipDALFactory;
|
||||
};
|
||||
|
||||
export type TMembershipServiceFactory = ReturnType<typeof membershipServiceFactory>;
|
||||
|
||||
export const membershipServiceFactory = ({ membershipDAL }: TMembershipServiceFactoryDep) => {
|
||||
return {};
|
||||
};
|
||||
0
backend/src/services/membership/membership-types.ts
Normal file
0
backend/src/services/membership/membership-types.ts
Normal file
@@ -44,11 +44,17 @@ export const roleServiceFactory = ({ roleDAL, permissionService }: TRoleServiceF
|
||||
const factory = scopeFactory[scopeData.scope];
|
||||
await factory.onCreateRoleGuard(dto);
|
||||
|
||||
const scope = factory.getScopeField(scopeData);
|
||||
const existingRole = await roleDAL.findOne({
|
||||
slug: data.slug,
|
||||
[scope.key]: scope.value
|
||||
});
|
||||
if (existingRole) throw new NotFoundError({ message: `Role with ${data.slug} exist` });
|
||||
|
||||
validateHandlebarTemplate("Role Creation", JSON.stringify(data.permissions || []), {
|
||||
allowedExpressions: (val) => val.includes("identity.")
|
||||
});
|
||||
|
||||
const scope = factory.getScopeField(scopeData);
|
||||
const role = await roleDAL.create({
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
|
||||
Reference in New Issue
Block a user