From 6f070ce7adb1c2db3080b1357d3ad90302870d2c Mon Sep 17 00:00:00 2001 From: Tal Rofe Date: Tue, 7 Jun 2022 10:53:47 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=94=A5=20support=20inline=20polic?= =?UTF-8?q?ies=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit support inline policies API --- apps/backend/prisma/schema.prisma | 42 ++++----- .../guards/belonging-group.guard.ts | 4 +- apps/backend/src/models/policy-library.ts | 6 ++ .../src/modules/database/database.module.ts | 5 +- .../src/modules/database/group.service.ts | 2 +- .../modules/database/inline-policy.service.ts | 85 +++++++++++++++++++ .../user/modules/groups/delete.controller.ts | 2 +- .../modules/groups/edit-label.controller.ts | 2 +- .../user/modules/groups/groups.module.ts | 3 +- .../inline-policies/add-rule.controller.ts | 40 +++++++++ .../inline-policies/classes/add-rule.dto.ts | 6 ++ .../classes/create-inline.dto.ts | 12 +++ .../classes/remove-rule.dto.ts | 7 ++ .../classes/update-configuration.dto.ts | 6 ++ .../commands/contracts/add-rule.contract.ts | 3 + .../contracts/delete-inline.contract.ts | 3 + .../contracts/remove-rule.contract.ts | 3 + .../update-configuration.contract.ts | 3 + .../commands/handlers/add-rule.handler.ts | 16 ++++ .../handlers/delete-inline.handler.ts | 14 +++ .../commands/handlers/index.ts | 11 +++ .../commands/handlers/remove-rule.handler.ts | 14 +++ .../handlers/update-configuration.handler.ts | 16 ++++ .../create-inline.controller.ts | 41 +++++++++ .../delete-inline.controller.ts | 33 +++++++ .../guards/belonging-inline-policy.guard.ts | 23 +++++ .../inline-policies/inline-policies.module.ts | 25 ++++++ .../inline-policies/inline-policies.routes.ts | 11 +++ .../inline-policies/interfaces/responses.ts | 3 + .../contracts/create-inline.contract.ts | 9 ++ .../queries/handlers/create-inline.handler.ts | 20 +++++ .../inline-policies/queries/handlers/index.ts | 3 + .../inline-policies/remove-rule.controller.ts | 33 +++++++ .../update-configuration.controller.ts | 30 +++++++ apps/backend/src/modules/user/user.module.ts | 3 +- apps/backend/src/modules/user/user.routes.ts | 6 ++ apps/backend/tsconfig.base.json | 3 +- 37 files changed, 519 insertions(+), 29 deletions(-) rename apps/backend/src/{modules/user/modules/groups => }/guards/belonging-group.guard.ts (83%) create mode 100644 apps/backend/src/models/policy-library.ts create mode 100644 apps/backend/src/modules/database/inline-policy.service.ts create mode 100644 apps/backend/src/modules/user/modules/inline-policies/add-rule.controller.ts create mode 100644 apps/backend/src/modules/user/modules/inline-policies/classes/add-rule.dto.ts create mode 100644 apps/backend/src/modules/user/modules/inline-policies/classes/create-inline.dto.ts create mode 100644 apps/backend/src/modules/user/modules/inline-policies/classes/remove-rule.dto.ts create mode 100644 apps/backend/src/modules/user/modules/inline-policies/classes/update-configuration.dto.ts create mode 100644 apps/backend/src/modules/user/modules/inline-policies/commands/contracts/add-rule.contract.ts create mode 100644 apps/backend/src/modules/user/modules/inline-policies/commands/contracts/delete-inline.contract.ts create mode 100644 apps/backend/src/modules/user/modules/inline-policies/commands/contracts/remove-rule.contract.ts create mode 100644 apps/backend/src/modules/user/modules/inline-policies/commands/contracts/update-configuration.contract.ts create mode 100644 apps/backend/src/modules/user/modules/inline-policies/commands/handlers/add-rule.handler.ts create mode 100644 apps/backend/src/modules/user/modules/inline-policies/commands/handlers/delete-inline.handler.ts create mode 100644 apps/backend/src/modules/user/modules/inline-policies/commands/handlers/index.ts create mode 100644 apps/backend/src/modules/user/modules/inline-policies/commands/handlers/remove-rule.handler.ts create mode 100644 apps/backend/src/modules/user/modules/inline-policies/commands/handlers/update-configuration.handler.ts create mode 100644 apps/backend/src/modules/user/modules/inline-policies/create-inline.controller.ts create mode 100644 apps/backend/src/modules/user/modules/inline-policies/delete-inline.controller.ts create mode 100644 apps/backend/src/modules/user/modules/inline-policies/guards/belonging-inline-policy.guard.ts create mode 100644 apps/backend/src/modules/user/modules/inline-policies/inline-policies.module.ts create mode 100644 apps/backend/src/modules/user/modules/inline-policies/inline-policies.routes.ts create mode 100644 apps/backend/src/modules/user/modules/inline-policies/interfaces/responses.ts create mode 100644 apps/backend/src/modules/user/modules/inline-policies/queries/contracts/create-inline.contract.ts create mode 100644 apps/backend/src/modules/user/modules/inline-policies/queries/handlers/create-inline.handler.ts create mode 100644 apps/backend/src/modules/user/modules/inline-policies/queries/handlers/index.ts create mode 100644 apps/backend/src/modules/user/modules/inline-policies/remove-rule.controller.ts create mode 100644 apps/backend/src/modules/user/modules/inline-policies/update-configuration.controller.ts diff --git a/apps/backend/prisma/schema.prisma b/apps/backend/prisma/schema.prisma index b4e059814..dec67fb14 100644 --- a/apps/backend/prisma/schema.prisma +++ b/apps/backend/prisma/schema.prisma @@ -17,18 +17,7 @@ enum PolicyLibrary { ESLINT PRETTIER INFLINT -} - -enum LibraryCategory { - LINTER -} - -type InlinePolicy { - label String - library PolicyLibrary - category LibraryCategory - configuration Json? - rules Json? + STYLELINT } model User { @@ -79,17 +68,33 @@ model ClientSecret { @@unique(fields: [userId, secret], name: "unique_user_secrets") } +model InlinePolicy { + id String @id @default(auto()) @map("_id") @db.ObjectId + + groupId String @db.ObjectId + label String + library PolicyLibrary + configuration Json? + rules Json? + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + group Group @relation(fields: [groupId], references: [id], onDelete: Cascade) +} + model Group { id String @id @default(auto()) @map("_id") @db.ObjectId - userId String @db.ObjectId - label String? - inlinePolicies InlinePolicy[] - policyIDs String[] @db.ObjectId + userId String @db.ObjectId + label String? + policyIDs String[] @db.ObjectId createdAt DateTime @default(now()) updatedAt DateTime @updatedAt + inlinePolicies InlinePolicy[] + user User @relation(fields: [userId], references: [id], onDelete: Cascade) policies Policy[] @relation(fields: [policyIDs], references: [id]) @@ -99,13 +104,12 @@ model Group { model Policy { id String @id @default(auto()) @map("_id") @db.ObjectId - userId String @db.ObjectId + userId String @db.ObjectId label String library PolicyLibrary - category LibraryCategory configuration Json? rules Json? - groupsIDs String[] @db.ObjectId + groupsIDs String[] @db.ObjectId createdAt DateTime @default(now()) updatedAt DateTime @updatedAt diff --git a/apps/backend/src/modules/user/modules/groups/guards/belonging-group.guard.ts b/apps/backend/src/guards/belonging-group.guard.ts similarity index 83% rename from apps/backend/src/modules/user/modules/groups/guards/belonging-group.guard.ts rename to apps/backend/src/guards/belonging-group.guard.ts index bdfaad575..645527aec 100644 --- a/apps/backend/src/modules/user/modules/groups/guards/belonging-group.guard.ts +++ b/apps/backend/src/guards/belonging-group.guard.ts @@ -13,8 +13,8 @@ export class BelongingGroupGuard implements CanActivate { const userId = user.sub; const groupId = request.params.group_id as string; - const groupBelongUser = await this.dbGroupService.doesGroupBelongUser(userId, groupId); + const groupBelongsUser = await this.dbGroupService.doesGroupBelongUser(userId, groupId); - return groupBelongUser; + return groupBelongsUser; } } diff --git a/apps/backend/src/models/policy-library.ts b/apps/backend/src/models/policy-library.ts new file mode 100644 index 000000000..71e70c89b --- /dev/null +++ b/apps/backend/src/models/policy-library.ts @@ -0,0 +1,6 @@ +export enum PolicyLibrary { + ESLINT = 'ESLINT', + STYLELINT = 'STYLELINT', + PRETTIER = 'PRETTIER', + INFLINT = 'INFLINT', +} diff --git a/apps/backend/src/modules/database/database.module.ts b/apps/backend/src/modules/database/database.module.ts index 4f8b9483f..3db3a65dd 100644 --- a/apps/backend/src/modules/database/database.module.ts +++ b/apps/backend/src/modules/database/database.module.ts @@ -2,12 +2,13 @@ import { Global, Module } from '@nestjs/common'; import { DBClientSecretService } from './client-secret.service'; import { DBGroupService } from './group.service'; +import { DBInlinePolicyService } from './inline-policy.service'; import { PrismaService } from './prisma.service'; import { DBUserService } from './user.service'; @Global() @Module({ - providers: [PrismaService, DBUserService, DBClientSecretService, DBGroupService], - exports: [DBUserService, DBClientSecretService, DBGroupService], + providers: [PrismaService, DBUserService, DBClientSecretService, DBGroupService, DBInlinePolicyService], + exports: [DBUserService, DBClientSecretService, DBGroupService, DBInlinePolicyService], }) export class DatabaseModule {} diff --git a/apps/backend/src/modules/database/group.service.ts b/apps/backend/src/modules/database/group.service.ts index 57cd39020..fcae91ebc 100644 --- a/apps/backend/src/modules/database/group.service.ts +++ b/apps/backend/src/modules/database/group.service.ts @@ -8,7 +8,7 @@ export class DBGroupService { public async createGroup(userId: string) { const createdGroup = await this.prisma.group.create({ - data: { userId, inlinePolicies: [], policyIDs: [] }, + data: { userId, policyIDs: [] }, select: { id: true }, }); diff --git a/apps/backend/src/modules/database/inline-policy.service.ts b/apps/backend/src/modules/database/inline-policy.service.ts new file mode 100644 index 000000000..a64258b55 --- /dev/null +++ b/apps/backend/src/modules/database/inline-policy.service.ts @@ -0,0 +1,85 @@ +import { Injectable } from '@nestjs/common'; +import { Prisma } from '@prisma/client'; + +import { PolicyLibrary } from '@/models/policy-library'; + +import { PrismaService } from './prisma.service'; + +@Injectable() +export class DBInlinePolicyService { + constructor(private prisma: PrismaService) {} + + public async createInlinePolicy(groupId: string, label: string, library: PolicyLibrary) { + const createdInlinePolicy = await this.prisma.inlinePolicy.create({ + data: { groupId, label, library }, + select: { id: true }, + }); + + return createdInlinePolicy.id; + } + + public async deleteInlinePolicy(inlinePolicyId: string) { + await this.prisma.inlinePolicy.delete({ where: { id: inlinePolicyId } }); + } + + public async updateConfiguration(inlinePolicyId: string, configuration: Record) { + await this.prisma.inlinePolicy.update({ + where: { id: inlinePolicyId }, + data: { configuration: configuration as Prisma.JsonObject }, + }); + } + + public async doesInlinePolicyBelongUser(inlinePolicyId: string, userId: string) { + const inlinePolicyDB = await this.prisma.inlinePolicy.findFirst({ + where: { id: inlinePolicyId, group: { userId } }, + }); + + return inlinePolicyDB !== null; + } + + public async addRule(inlinePolicyId: string, rule: Record) { + const inlinePolicyDB = await this.prisma.inlinePolicy.findFirst({ + where: { id: inlinePolicyId }, + select: { rules: true }, + rejectOnNotFound: true, + }); + + let newInlinePolicyRules: Prisma.JsonObject; + + if (!inlinePolicyDB.rules) { + newInlinePolicyRules = rule as Prisma.JsonObject; + } else { + newInlinePolicyRules = { + ...(inlinePolicyDB.rules as Prisma.JsonObject), + ...rule, + } as Prisma.JsonObject; + } + + await this.prisma.inlinePolicy.update({ + where: { id: inlinePolicyId }, + data: { rules: newInlinePolicyRules }, + }); + } + + public async removeRule(inlinePolicyId: string, ruleName: string) { + const inlinePolicyDB = await this.prisma.inlinePolicy.findFirst({ + where: { id: inlinePolicyId }, + select: { rules: true }, + rejectOnNotFound: true, + }); + + if (!inlinePolicyDB.rules) { + return; + } + + const rulesWithoutRule = { + ...(inlinePolicyDB.rules as Prisma.JsonObject), + [ruleName]: undefined, + }; + + await this.prisma.inlinePolicy.update({ + where: { id: inlinePolicyId }, + data: { rules: rulesWithoutRule }, + }); + } +} diff --git a/apps/backend/src/modules/user/modules/groups/delete.controller.ts b/apps/backend/src/modules/user/modules/groups/delete.controller.ts index c8b434df4..54e5b26ee 100644 --- a/apps/backend/src/modules/user/modules/groups/delete.controller.ts +++ b/apps/backend/src/modules/user/modules/groups/delete.controller.ts @@ -2,9 +2,9 @@ import { Controller, Delete, HttpCode, HttpStatus, Logger, Param, UseGuards } fr import { CommandBus } from '@nestjs/cqrs'; import { CurrentUserId } from '@/decorators/current-user-id.decorator'; +import { BelongingGroupGuard } from '@/guards/belonging-group.guard'; import Routes from './groups.routes'; -import { BelongingGroupGuard } from './guards/belonging-group.guard'; import { DeleteContract } from './commands/contracts/delete.contract'; @Controller(Routes.CONTROLLER) diff --git a/apps/backend/src/modules/user/modules/groups/edit-label.controller.ts b/apps/backend/src/modules/user/modules/groups/edit-label.controller.ts index a2e1fdcb8..0a2d8c2b9 100644 --- a/apps/backend/src/modules/user/modules/groups/edit-label.controller.ts +++ b/apps/backend/src/modules/user/modules/groups/edit-label.controller.ts @@ -2,11 +2,11 @@ import { Body, Controller, HttpCode, HttpStatus, Logger, Param, Patch, UseGuards import { CommandBus } from '@nestjs/cqrs'; import { CurrentUserId } from '@/decorators/current-user-id.decorator'; +import { BelongingGroupGuard } from '@/guards/belonging-group.guard'; import Routes from './groups.routes'; import { EditLabelDto } from './classes/edit-label.dto'; import { EditLabelContract } from './commands/contracts/edit-label.contract'; -import { BelongingGroupGuard } from './guards/belonging-group.guard'; @Controller(Routes.CONTROLLER) export class EditLabelController { diff --git a/apps/backend/src/modules/user/modules/groups/groups.module.ts b/apps/backend/src/modules/user/modules/groups/groups.module.ts index 3ef4f0506..db2668b03 100644 --- a/apps/backend/src/modules/user/modules/groups/groups.module.ts +++ b/apps/backend/src/modules/user/modules/groups/groups.module.ts @@ -1,10 +1,11 @@ import { Module } from '@nestjs/common'; +import { BelongingGroupGuard } from '@/guards/belonging-group.guard'; + import { CommandHandlers } from './commands/handlers'; import { CreateController } from './create.controller'; import { DeleteController } from './delete.controller'; import { EditLabelController } from './edit-label.controller'; -import { BelongingGroupGuard } from './guards/belonging-group.guard'; import { QueryHandlers } from './queries/handlers'; @Module({ diff --git a/apps/backend/src/modules/user/modules/inline-policies/add-rule.controller.ts b/apps/backend/src/modules/user/modules/inline-policies/add-rule.controller.ts new file mode 100644 index 000000000..3adb57bc8 --- /dev/null +++ b/apps/backend/src/modules/user/modules/inline-policies/add-rule.controller.ts @@ -0,0 +1,40 @@ +import { Body, Controller, Logger, Param, Post, UseGuards } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; + +import Routes from './inline-policies.routes'; +import { BelongingInlinePolicyGuard } from './guards/belonging-inline-policy.guard'; +import { AddRuleDto } from './classes/add-rule.dto'; +import { AddRuleContract } from './commands/contracts/add-rule.contract'; + +@Controller(Routes.CONTROLLER) +export class AddRuleController { + private readonly logger = new Logger(AddRuleController.name); + + constructor(private readonly commandBus: CommandBus) {} + + @UseGuards(BelongingInlinePolicyGuard) + @Post(Routes.ADD_RULE) + public async addRule( + @Param('policy_id') policyId: string, + @Body() addRuleDto: AddRuleDto, + ): Promise { + this.logger.log(`Will try to add rule for an inline policy with an Id: "${policyId}"`); + + await this.commandBus.execute(new AddRuleContract(policyId, addRuleDto.rule)); + + this.logger.log(`Successfully added a rule for an inline policy Id: "${policyId}"`); + } + + @UseGuards(BelongingInlinePolicyGuard) + @Post(Routes.EDIT_RULE) + public async editRule( + @Param('policy_id') policyId: string, + @Body() editRuleDto: AddRuleDto, + ): Promise { + this.logger.log(`Will try to edit rule for an inline policy with an Id: "${policyId}"`); + + await this.commandBus.execute(new AddRuleContract(policyId, editRuleDto.rule)); + + this.logger.log(`Successfully edited a rule for an inline policy Id: "${policyId}"`); + } +} diff --git a/apps/backend/src/modules/user/modules/inline-policies/classes/add-rule.dto.ts b/apps/backend/src/modules/user/modules/inline-policies/classes/add-rule.dto.ts new file mode 100644 index 000000000..ad471c2ed --- /dev/null +++ b/apps/backend/src/modules/user/modules/inline-policies/classes/add-rule.dto.ts @@ -0,0 +1,6 @@ +import { IsJSON } from 'class-validator'; + +export class AddRuleDto { + @IsJSON() + readonly rule!: string; +} diff --git a/apps/backend/src/modules/user/modules/inline-policies/classes/create-inline.dto.ts b/apps/backend/src/modules/user/modules/inline-policies/classes/create-inline.dto.ts new file mode 100644 index 000000000..e7148499e --- /dev/null +++ b/apps/backend/src/modules/user/modules/inline-policies/classes/create-inline.dto.ts @@ -0,0 +1,12 @@ +import { IsEnum, IsString, MinLength } from 'class-validator'; + +import { PolicyLibrary } from '@/models/policy-library'; + +export class CreateInlineDto { + @IsString() + @MinLength(1) + readonly label!: string; + + @IsEnum(PolicyLibrary) + readonly library!: PolicyLibrary; +} diff --git a/apps/backend/src/modules/user/modules/inline-policies/classes/remove-rule.dto.ts b/apps/backend/src/modules/user/modules/inline-policies/classes/remove-rule.dto.ts new file mode 100644 index 000000000..4d223a949 --- /dev/null +++ b/apps/backend/src/modules/user/modules/inline-policies/classes/remove-rule.dto.ts @@ -0,0 +1,7 @@ +import { IsString, MinLength } from 'class-validator'; + +export class RemoveRuleDto { + @IsString() + @MinLength(1) + readonly ruleName!: string; +} diff --git a/apps/backend/src/modules/user/modules/inline-policies/classes/update-configuration.dto.ts b/apps/backend/src/modules/user/modules/inline-policies/classes/update-configuration.dto.ts new file mode 100644 index 000000000..967adc2ae --- /dev/null +++ b/apps/backend/src/modules/user/modules/inline-policies/classes/update-configuration.dto.ts @@ -0,0 +1,6 @@ +import { IsJSON } from 'class-validator'; + +export class UpdateConfigurationDto { + @IsJSON() + readonly configuration!: string; +} diff --git a/apps/backend/src/modules/user/modules/inline-policies/commands/contracts/add-rule.contract.ts b/apps/backend/src/modules/user/modules/inline-policies/commands/contracts/add-rule.contract.ts new file mode 100644 index 000000000..442ad7882 --- /dev/null +++ b/apps/backend/src/modules/user/modules/inline-policies/commands/contracts/add-rule.contract.ts @@ -0,0 +1,3 @@ +export class AddRuleContract { + constructor(public readonly policyId: string, public readonly rule: string) {} +} diff --git a/apps/backend/src/modules/user/modules/inline-policies/commands/contracts/delete-inline.contract.ts b/apps/backend/src/modules/user/modules/inline-policies/commands/contracts/delete-inline.contract.ts new file mode 100644 index 000000000..6df58e988 --- /dev/null +++ b/apps/backend/src/modules/user/modules/inline-policies/commands/contracts/delete-inline.contract.ts @@ -0,0 +1,3 @@ +export class DeleteInlineContract { + constructor(public readonly policyId: string) {} +} diff --git a/apps/backend/src/modules/user/modules/inline-policies/commands/contracts/remove-rule.contract.ts b/apps/backend/src/modules/user/modules/inline-policies/commands/contracts/remove-rule.contract.ts new file mode 100644 index 000000000..b9fa31747 --- /dev/null +++ b/apps/backend/src/modules/user/modules/inline-policies/commands/contracts/remove-rule.contract.ts @@ -0,0 +1,3 @@ +export class RemoveRuleContract { + constructor(public readonly policyId: string, public readonly ruleName: string) {} +} diff --git a/apps/backend/src/modules/user/modules/inline-policies/commands/contracts/update-configuration.contract.ts b/apps/backend/src/modules/user/modules/inline-policies/commands/contracts/update-configuration.contract.ts new file mode 100644 index 000000000..98b67a4e3 --- /dev/null +++ b/apps/backend/src/modules/user/modules/inline-policies/commands/contracts/update-configuration.contract.ts @@ -0,0 +1,3 @@ +export class UpdateConfigurationContract { + constructor(public readonly policyId: string, public readonly configuration: string) {} +} diff --git a/apps/backend/src/modules/user/modules/inline-policies/commands/handlers/add-rule.handler.ts b/apps/backend/src/modules/user/modules/inline-policies/commands/handlers/add-rule.handler.ts new file mode 100644 index 000000000..d740388ad --- /dev/null +++ b/apps/backend/src/modules/user/modules/inline-policies/commands/handlers/add-rule.handler.ts @@ -0,0 +1,16 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { DBInlinePolicyService } from '@/modules/database/inline-policy.service'; + +import { AddRuleContract } from '../contracts/add-rule.contract'; + +@CommandHandler(AddRuleContract) +export class AddRuleHandler implements ICommandHandler { + constructor(private readonly dbInlinePolicyService: DBInlinePolicyService) {} + + async execute(contract: AddRuleContract) { + const parsedRule = JSON.parse(contract.rule); + + await this.dbInlinePolicyService.addRule(contract.policyId, parsedRule); + } +} diff --git a/apps/backend/src/modules/user/modules/inline-policies/commands/handlers/delete-inline.handler.ts b/apps/backend/src/modules/user/modules/inline-policies/commands/handlers/delete-inline.handler.ts new file mode 100644 index 000000000..d653e5775 --- /dev/null +++ b/apps/backend/src/modules/user/modules/inline-policies/commands/handlers/delete-inline.handler.ts @@ -0,0 +1,14 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { DBInlinePolicyService } from '@/modules/database/inline-policy.service'; + +import { DeleteInlineContract } from '../contracts/delete-inline.contract'; + +@CommandHandler(DeleteInlineContract) +export class DeleteInlineHandler implements ICommandHandler { + constructor(private readonly dbInlinePolicyService: DBInlinePolicyService) {} + + async execute(contract: DeleteInlineContract) { + await this.dbInlinePolicyService.deleteInlinePolicy(contract.policyId); + } +} diff --git a/apps/backend/src/modules/user/modules/inline-policies/commands/handlers/index.ts b/apps/backend/src/modules/user/modules/inline-policies/commands/handlers/index.ts new file mode 100644 index 000000000..afde58259 --- /dev/null +++ b/apps/backend/src/modules/user/modules/inline-policies/commands/handlers/index.ts @@ -0,0 +1,11 @@ +import { AddRuleHandler } from './add-rule.handler'; +import { DeleteInlineHandler } from './delete-inline.handler'; +import { RemoveRuleHandler } from './remove-rule.handler'; +import { UpdateConfigurationHandler } from './update-configuration.handler'; + +export const CommandHandlers = [ + DeleteInlineHandler, + UpdateConfigurationHandler, + AddRuleHandler, + RemoveRuleHandler, +]; diff --git a/apps/backend/src/modules/user/modules/inline-policies/commands/handlers/remove-rule.handler.ts b/apps/backend/src/modules/user/modules/inline-policies/commands/handlers/remove-rule.handler.ts new file mode 100644 index 000000000..45300ebbe --- /dev/null +++ b/apps/backend/src/modules/user/modules/inline-policies/commands/handlers/remove-rule.handler.ts @@ -0,0 +1,14 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { DBInlinePolicyService } from '@/modules/database/inline-policy.service'; + +import { RemoveRuleContract } from '../contracts/remove-rule.contract'; + +@CommandHandler(RemoveRuleContract) +export class RemoveRuleHandler implements ICommandHandler { + constructor(private readonly dbInlinePolicyService: DBInlinePolicyService) {} + + async execute(contract: RemoveRuleContract) { + await this.dbInlinePolicyService.removeRule(contract.policyId, contract.ruleName); + } +} diff --git a/apps/backend/src/modules/user/modules/inline-policies/commands/handlers/update-configuration.handler.ts b/apps/backend/src/modules/user/modules/inline-policies/commands/handlers/update-configuration.handler.ts new file mode 100644 index 000000000..0ed101e23 --- /dev/null +++ b/apps/backend/src/modules/user/modules/inline-policies/commands/handlers/update-configuration.handler.ts @@ -0,0 +1,16 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { DBInlinePolicyService } from '@/modules/database/inline-policy.service'; + +import { UpdateConfigurationContract } from '../contracts/update-configuration.contract'; + +@CommandHandler(UpdateConfigurationContract) +export class UpdateConfigurationHandler implements ICommandHandler { + constructor(private readonly dbInlinePolicyService: DBInlinePolicyService) {} + + async execute(contract: UpdateConfigurationContract) { + const parsedConfiguration = JSON.parse(contract.configuration); + + await this.dbInlinePolicyService.updateConfiguration(contract.policyId, parsedConfiguration); + } +} diff --git a/apps/backend/src/modules/user/modules/inline-policies/create-inline.controller.ts b/apps/backend/src/modules/user/modules/inline-policies/create-inline.controller.ts new file mode 100644 index 000000000..90204c6bf --- /dev/null +++ b/apps/backend/src/modules/user/modules/inline-policies/create-inline.controller.ts @@ -0,0 +1,41 @@ +import { Body, Controller, Logger, Param, Post, UseGuards } from '@nestjs/common'; +import { QueryBus } from '@nestjs/cqrs'; + +import { CurrentUserId } from '@/decorators/current-user-id.decorator'; +import { BelongingGroupGuard } from '@/guards/belonging-group.guard'; + +import Routes from './inline-policies.routes'; +import { CreateInlineDto } from './classes/create-inline.dto'; +import { CreateInlineContract } from './queries/contracts/create-inline.contract'; +import { ICreateInlinePolicy } from './interfaces/responses'; + +@Controller(Routes.CONTROLLER) +export class CreateInlineController { + private readonly logger = new Logger(CreateInlineController.name); + + constructor(private readonly queryBus: QueryBus) {} + + @UseGuards(BelongingGroupGuard) + @Post(Routes.CREATE) + public async createInline( + @CurrentUserId() userId: string, + @Param('group_id') groupId: string, + @Body() createInlineDto: CreateInlineDto, + ): Promise { + this.logger.log( + `Will try to create an inline policy for a user with an Id: "${userId}" and for group with Id: "${groupId}". Label is "${createInlineDto.label}"`, + ); + + const createdInlinePolicyId = await this.queryBus.execute( + new CreateInlineContract(groupId, createInlineDto.label, createInlineDto.library), + ); + + this.logger.log( + `Successfully created an inline policy for a user with an Id: "${userId}" and for group with Id: "${groupId}". Label is "${createInlineDto.label}"`, + ); + + return { + policyId: createdInlinePolicyId, + }; + } +} diff --git a/apps/backend/src/modules/user/modules/inline-policies/delete-inline.controller.ts b/apps/backend/src/modules/user/modules/inline-policies/delete-inline.controller.ts new file mode 100644 index 000000000..0e0b72102 --- /dev/null +++ b/apps/backend/src/modules/user/modules/inline-policies/delete-inline.controller.ts @@ -0,0 +1,33 @@ +import { Controller, HttpCode, HttpStatus, Logger, Param, Post, UseGuards } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; + +import { CurrentUserId } from '@/decorators/current-user-id.decorator'; + +import Routes from './inline-policies.routes'; +import { DeleteInlineContract } from './commands/contracts/delete-inline.contract'; +import { BelongingInlinePolicyGuard } from './guards/belonging-inline-policy.guard'; + +@Controller(Routes.CONTROLLER) +export class DeleteInlineController { + private readonly logger = new Logger(DeleteInlineController.name); + + constructor(private readonly commandBus: CommandBus) {} + + @UseGuards(BelongingInlinePolicyGuard) + @Post(Routes.DELETE) + @HttpCode(HttpStatus.OK) + public async deleteInline( + @CurrentUserId() userId: string, + @Param('policy_id') policyId: string, + ): Promise { + this.logger.log( + `Will try to delete an inline policy for a user with an Id: "${userId}" and an inline policy Id: "${policyId}"`, + ); + + await this.commandBus.execute(new DeleteInlineContract(policyId)); + + this.logger.log( + `Successfully deleted an inline policy for a user with an Id: "${userId}" and an inline policy Id: "${policyId}"`, + ); + } +} diff --git a/apps/backend/src/modules/user/modules/inline-policies/guards/belonging-inline-policy.guard.ts b/apps/backend/src/modules/user/modules/inline-policies/guards/belonging-inline-policy.guard.ts new file mode 100644 index 000000000..a0430abcf --- /dev/null +++ b/apps/backend/src/modules/user/modules/inline-policies/guards/belonging-inline-policy.guard.ts @@ -0,0 +1,23 @@ +import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; + +import { IJwtTokenPayload } from '@/interfaces/jwt-token'; +import { DBInlinePolicyService } from '@/modules/database/inline-policy.service'; + +@Injectable() +export class BelongingInlinePolicyGuard implements CanActivate { + constructor(private readonly dbInlinePolicyService: DBInlinePolicyService) {} + + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const user = request.user as IJwtTokenPayload; + const userId = user.sub; + const inlinePolicyId = request.params.policy_id as string; + + const groupBelongUser = await this.dbInlinePolicyService.doesInlinePolicyBelongUser( + userId, + inlinePolicyId, + ); + + return groupBelongUser; + } +} diff --git a/apps/backend/src/modules/user/modules/inline-policies/inline-policies.module.ts b/apps/backend/src/modules/user/modules/inline-policies/inline-policies.module.ts new file mode 100644 index 000000000..0744a34e4 --- /dev/null +++ b/apps/backend/src/modules/user/modules/inline-policies/inline-policies.module.ts @@ -0,0 +1,25 @@ +import { Module } from '@nestjs/common'; + +import { BelongingGroupGuard } from '@/guards/belonging-group.guard'; + +import { CommandHandlers } from './commands/handlers'; +import { QueryHandlers } from './queries/handlers'; +import { CreateInlineController } from './create-inline.controller'; +import { DeleteInlineController } from './delete-inline.controller'; +import { BelongingInlinePolicyGuard } from './guards/belonging-inline-policy.guard'; +import { UpdateConfigurationController } from './update-configuration.controller'; +import { AddRuleController } from './add-rule.controller'; +import { RemoveRuleController } from './remove-rule.controller'; + +@Module({ + imports: [], + controllers: [ + CreateInlineController, + DeleteInlineController, + UpdateConfigurationController, + AddRuleController, + RemoveRuleController, + ], + providers: [BelongingGroupGuard, BelongingInlinePolicyGuard, ...CommandHandlers, ...QueryHandlers], +}) +export class InlinePoliciesModule {} diff --git a/apps/backend/src/modules/user/modules/inline-policies/inline-policies.routes.ts b/apps/backend/src/modules/user/modules/inline-policies/inline-policies.routes.ts new file mode 100644 index 000000000..d0a89cfdf --- /dev/null +++ b/apps/backend/src/modules/user/modules/inline-policies/inline-policies.routes.ts @@ -0,0 +1,11 @@ +const Routes = { + CONTROLLER: 'inline-policies', + CREATE: 'create/:group_id', + DELETE: 'delete/:policy_id', + UPDATE_CONFIGURATION: 'update-configuration/:policy_id', + ADD_RULE: 'add-rule/:policy_id', + EDIT_RULE: 'edit-rule/:policy_id', + REMOVE_RULE: 'remove-rule/:policy_id', +}; + +export default Routes; diff --git a/apps/backend/src/modules/user/modules/inline-policies/interfaces/responses.ts b/apps/backend/src/modules/user/modules/inline-policies/interfaces/responses.ts new file mode 100644 index 000000000..37d35337e --- /dev/null +++ b/apps/backend/src/modules/user/modules/inline-policies/interfaces/responses.ts @@ -0,0 +1,3 @@ +export interface ICreateInlinePolicy { + policyId: string; +} diff --git a/apps/backend/src/modules/user/modules/inline-policies/queries/contracts/create-inline.contract.ts b/apps/backend/src/modules/user/modules/inline-policies/queries/contracts/create-inline.contract.ts new file mode 100644 index 000000000..09752b848 --- /dev/null +++ b/apps/backend/src/modules/user/modules/inline-policies/queries/contracts/create-inline.contract.ts @@ -0,0 +1,9 @@ +import { PolicyLibrary } from '@/models/policy-library'; + +export class CreateInlineContract { + constructor( + public readonly groupId: string, + public readonly label: string, + public readonly library: PolicyLibrary, + ) {} +} diff --git a/apps/backend/src/modules/user/modules/inline-policies/queries/handlers/create-inline.handler.ts b/apps/backend/src/modules/user/modules/inline-policies/queries/handlers/create-inline.handler.ts new file mode 100644 index 000000000..b61926dd6 --- /dev/null +++ b/apps/backend/src/modules/user/modules/inline-policies/queries/handlers/create-inline.handler.ts @@ -0,0 +1,20 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { DBInlinePolicyService } from '@/modules/database/inline-policy.service'; + +import { CreateInlineContract } from '../contracts/create-inline.contract'; + +@CommandHandler(CreateInlineContract) +export class CreateInlineHandler implements ICommandHandler { + constructor(private readonly dbInlinePolicyService: DBInlinePolicyService) {} + + async execute(contract: CreateInlineContract) { + const createdInlinePolicyId = await this.dbInlinePolicyService.createInlinePolicy( + contract.groupId, + contract.label, + contract.library, + ); + + return createdInlinePolicyId; + } +} diff --git a/apps/backend/src/modules/user/modules/inline-policies/queries/handlers/index.ts b/apps/backend/src/modules/user/modules/inline-policies/queries/handlers/index.ts new file mode 100644 index 000000000..635244a32 --- /dev/null +++ b/apps/backend/src/modules/user/modules/inline-policies/queries/handlers/index.ts @@ -0,0 +1,3 @@ +import { CreateInlineHandler } from './create-inline.handler'; + +export const QueryHandlers = [CreateInlineHandler]; diff --git a/apps/backend/src/modules/user/modules/inline-policies/remove-rule.controller.ts b/apps/backend/src/modules/user/modules/inline-policies/remove-rule.controller.ts new file mode 100644 index 000000000..b3f7b9d35 --- /dev/null +++ b/apps/backend/src/modules/user/modules/inline-policies/remove-rule.controller.ts @@ -0,0 +1,33 @@ +import { Body, Controller, Logger, Param, Post, UseGuards } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; + +import Routes from './inline-policies.routes'; +import { BelongingInlinePolicyGuard } from './guards/belonging-inline-policy.guard'; +import { RemoveRuleDto } from './classes/remove-rule.dto'; +import { RemoveRuleContract } from './commands/contracts/remove-rule.contract'; + +@Controller(Routes.CONTROLLER) +export class RemoveRuleController { + private readonly logger = new Logger(RemoveRuleController.name); + + constructor(private readonly commandBus: CommandBus) {} + + @UseGuards(BelongingInlinePolicyGuard) + @Post(Routes.EDIT_RULE) + public async removeRule( + @Param('policy_id') policyId: string, + @Body() removeRuleDto: RemoveRuleDto, + ): Promise { + this.logger.log( + `Will try to remove rule "${removeRuleDto.ruleName}" for an inline policy with an Id: "${policyId}"`, + ); + + await this.commandBus.execute( + new RemoveRuleContract(policyId, removeRuleDto.ruleName), + ); + + this.logger.log( + `Successfully removed a rule "${removeRuleDto.ruleName}" for an inline policy Id: "${policyId}"`, + ); + } +} diff --git a/apps/backend/src/modules/user/modules/inline-policies/update-configuration.controller.ts b/apps/backend/src/modules/user/modules/inline-policies/update-configuration.controller.ts new file mode 100644 index 000000000..0fd211831 --- /dev/null +++ b/apps/backend/src/modules/user/modules/inline-policies/update-configuration.controller.ts @@ -0,0 +1,30 @@ +import { Body, Controller, HttpCode, HttpStatus, Logger, Param, Post, UseGuards } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; + +import Routes from './inline-policies.routes'; +import { BelongingInlinePolicyGuard } from './guards/belonging-inline-policy.guard'; +import { UpdateConfigurationDto } from './classes/update-configuration.dto'; +import { UpdateConfigurationContract } from './commands/contracts/update-configuration.contract'; + +@Controller(Routes.CONTROLLER) +export class UpdateConfigurationController { + private readonly logger = new Logger(UpdateConfigurationController.name); + + constructor(private readonly commandBus: CommandBus) {} + + @UseGuards(BelongingInlinePolicyGuard) + @Post(Routes.UPDATE_CONFIGURATION) + @HttpCode(HttpStatus.OK) + public async updateConfiguration( + @Param('policy_id') policyId: string, + @Body() updateConfigurationDto: UpdateConfigurationDto, + ): Promise { + this.logger.log(`Will try to update configuration for an inline policy with an Id: "${policyId}"`); + + await this.commandBus.execute( + new UpdateConfigurationContract(policyId, updateConfigurationDto.configuration), + ); + + this.logger.log(`Successfully updated a configuration for an inline policy Id: "${policyId}"`); + } +} diff --git a/apps/backend/src/modules/user/user.module.ts b/apps/backend/src/modules/user/user.module.ts index 20ba232b8..dfaab9024 100644 --- a/apps/backend/src/modules/user/user.module.ts +++ b/apps/backend/src/modules/user/user.module.ts @@ -2,9 +2,10 @@ import { Module } from '@nestjs/common'; import { AuthModule } from './modules/auth/auth.module'; import { GroupsModule } from './modules/groups/groups.module'; +import { InlinePoliciesModule } from './modules/inline-policies/inline-policies.module'; import { SecretsModule } from './modules/secrets/secrets.module'; @Module({ - imports: [AuthModule, SecretsModule, GroupsModule], + imports: [AuthModule, SecretsModule, GroupsModule, InlinePoliciesModule], }) export class UserModule {} diff --git a/apps/backend/src/modules/user/user.routes.ts b/apps/backend/src/modules/user/user.routes.ts index f79ae8711..8b82ef414 100644 --- a/apps/backend/src/modules/user/user.routes.ts +++ b/apps/backend/src/modules/user/user.routes.ts @@ -1,6 +1,8 @@ import { Routes } from '@nestjs/core'; + import { AuthModule } from './modules/auth/auth.module'; import { GroupsModule } from './modules/groups/groups.module'; +import { InlinePoliciesModule } from './modules/inline-policies/inline-policies.module'; import { SecretsModule } from './modules/secrets/secrets.module'; export const userRoutes: Routes = [ @@ -16,4 +18,8 @@ export const userRoutes: Routes = [ path: '/', module: GroupsModule, }, + { + path: '/', + module: InlinePoliciesModule, + }, ]; diff --git a/apps/backend/tsconfig.base.json b/apps/backend/tsconfig.base.json index b828acebb..349df1fd1 100644 --- a/apps/backend/tsconfig.base.json +++ b/apps/backend/tsconfig.base.json @@ -10,7 +10,8 @@ "@/config/*": ["src/config/*"], "@/decorators/*": ["src/decorators/*"], "@/models/*": ["src/models/*"], - "@/interfaces/*": ["src/interfaces/*"] + "@/interfaces/*": ["src/interfaces/*"], + "@/guards/*": ["src/guards/*"] }, "typeRoots": ["./node_modules/@types", "./@types"] }